@delorenj/pjangler 1.5.49 → 1.5.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1065 -624
- package/dist/mcp-server.js +953 -512
- package/package.json +2 -1
package/dist/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,19 +1420,19 @@ 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
1438
|
function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
@@ -1450,7 +1450,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1450
1450
|
ok: true,
|
|
1451
1451
|
skipped: true,
|
|
1452
1452
|
logs: [
|
|
1453
|
-
`ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${
|
|
1453
|
+
`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
1454
|
]
|
|
1455
1455
|
};
|
|
1456
1456
|
}
|
|
@@ -1464,12 +1464,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1464
1464
|
};
|
|
1465
1465
|
}
|
|
1466
1466
|
const redact = (text2) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text2);
|
|
1467
|
-
const staging =
|
|
1467
|
+
const staging = mkdtempSync3(join18(tmpdir2(), "pjangler-tp-"));
|
|
1468
1468
|
try {
|
|
1469
|
-
const providersDir =
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1469
|
+
const providersDir = join18(staging, ".tp", "adapters");
|
|
1470
|
+
mkdirSync8(providersDir, { recursive: true });
|
|
1471
|
+
writeFileSync10(
|
|
1472
|
+
join18(staging, ".project.json"),
|
|
1473
1473
|
`${JSON.stringify(
|
|
1474
1474
|
{
|
|
1475
1475
|
project_name: action.boardName,
|
|
@@ -1488,7 +1488,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
|
|
|
1488
1488
|
`,
|
|
1489
1489
|
"utf8"
|
|
1490
1490
|
);
|
|
1491
|
-
const staged =
|
|
1491
|
+
const staged = join18(providersDir, `${provider}.sh`);
|
|
1492
1492
|
copyFileSync2(adapter, staged);
|
|
1493
1493
|
const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
|
|
1494
1494
|
if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
|
|
@@ -1584,7 +1584,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
|
|
|
1584
1584
|
const override = env2.PJ_AGENT_HOOKS_LAYER;
|
|
1585
1585
|
if (override === "0" || override === "false") return false;
|
|
1586
1586
|
if (override === "1" || override === "true") return true;
|
|
1587
|
-
return !existsSync11(
|
|
1587
|
+
return !existsSync11(join18(homedir7(), ".agents", "hooks"));
|
|
1588
1588
|
}
|
|
1589
1589
|
function jsonStable(value) {
|
|
1590
1590
|
return JSON.stringify(value, (_key, item) => isRecord3(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
|
|
@@ -1626,10 +1626,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1626
1626
|
const expanded = expandHome(sourceSkill);
|
|
1627
1627
|
const direct = resolve11(expanded);
|
|
1628
1628
|
if (existsSync11(direct)) return direct;
|
|
1629
|
-
const name =
|
|
1629
|
+
const name = basename9(sourceSkill);
|
|
1630
1630
|
const roots = sourceSkillRoots(env2);
|
|
1631
1631
|
for (const root of roots) {
|
|
1632
|
-
const candidate =
|
|
1632
|
+
const candidate = join18(root, name);
|
|
1633
1633
|
if (existsSync11(candidate)) return candidate;
|
|
1634
1634
|
}
|
|
1635
1635
|
const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
|
|
@@ -1639,8 +1639,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
|
|
|
1639
1639
|
function planProjectInit(input) {
|
|
1640
1640
|
if (!input.name.trim()) throw new Error("Project name is required");
|
|
1641
1641
|
const targetDir = resolve11(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
|
|
1642
|
-
const localManifestPath =
|
|
1643
|
-
const localManifestContent = existsSync11(localManifestPath) ?
|
|
1642
|
+
const localManifestPath = join18(targetDir, ".project.json");
|
|
1643
|
+
const localManifestContent = existsSync11(localManifestPath) ? readFileSync17(localManifestPath, "utf8") : void 0;
|
|
1644
1644
|
const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
|
|
1645
1645
|
const localId = localManifest?.project_id ?? localManifest?.project_slug;
|
|
1646
1646
|
const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
|
|
@@ -1668,7 +1668,7 @@ function planProjectInit(input) {
|
|
|
1668
1668
|
const recordedTypeValue = existing?.template?.commonproject?.project_type;
|
|
1669
1669
|
const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
|
|
1670
1670
|
const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
|
|
1671
|
-
const freshRender = scaffold && localManifest === void 0 && !existsSync11(
|
|
1671
|
+
const freshRender = scaffold && localManifest === void 0 && !existsSync11(join18(targetDir, ".copier-answers.yml"));
|
|
1672
1672
|
if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
|
|
1673
1673
|
const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
|
|
1674
1674
|
if (!adoptingPlugin) {
|
|
@@ -1766,7 +1766,7 @@ function planProjectInit(input) {
|
|
|
1766
1766
|
}));
|
|
1767
1767
|
}
|
|
1768
1768
|
actions.push(
|
|
1769
|
-
{ kind: "project.write-manifest", path:
|
|
1769
|
+
{ kind: "project.write-manifest", path: join18(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
|
|
1770
1770
|
{
|
|
1771
1771
|
kind: "ticket-provider.create-or-link",
|
|
1772
1772
|
enabled: boardEnabled,
|
|
@@ -1821,12 +1821,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1821
1821
|
plan.manifest.ticket_provider = manifestProvider;
|
|
1822
1822
|
action.boardId = boardId;
|
|
1823
1823
|
action.state = manifestProvider.state;
|
|
1824
|
-
const manifestPath =
|
|
1824
|
+
const manifestPath = join18(action.repoPath, ".project.json");
|
|
1825
1825
|
let next;
|
|
1826
1826
|
if (existsSync11(manifestPath)) {
|
|
1827
1827
|
let existing = {};
|
|
1828
1828
|
try {
|
|
1829
|
-
const parsed = JSON.parse(
|
|
1829
|
+
const parsed = JSON.parse(readFileSync17(manifestPath, "utf8"));
|
|
1830
1830
|
if (isRecord3(parsed)) existing = parsed;
|
|
1831
1831
|
} catch {
|
|
1832
1832
|
existing = {};
|
|
@@ -1834,13 +1834,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
|
|
|
1834
1834
|
const existingProvider = isRecord3(existing.ticket_provider) ? existing.ticket_provider : {};
|
|
1835
1835
|
next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
|
|
1836
1836
|
} else {
|
|
1837
|
-
|
|
1837
|
+
mkdirSync8(dirname14(manifestPath), { recursive: true });
|
|
1838
1838
|
next = plan.manifest;
|
|
1839
1839
|
}
|
|
1840
1840
|
const text2 = `${JSON.stringify(next, null, 2)}
|
|
1841
1841
|
`;
|
|
1842
|
-
if (!existsSync11(manifestPath) ||
|
|
1843
|
-
|
|
1842
|
+
if (!existsSync11(manifestPath) || readFileSync17(manifestPath, "utf8") !== text2) {
|
|
1843
|
+
writeFileSync10(manifestPath, text2, "utf8");
|
|
1844
1844
|
return [manifestPath];
|
|
1845
1845
|
}
|
|
1846
1846
|
return [];
|
|
@@ -1868,7 +1868,7 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1868
1868
|
logs.push(
|
|
1869
1869
|
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
1870
|
);
|
|
1871
|
-
|
|
1871
|
+
mkdirSync8(dirname14(action.targetDir), { recursive: true });
|
|
1872
1872
|
const before = snapshotTree(action.targetDir);
|
|
1873
1873
|
const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
|
|
1874
1874
|
const copierEnv = { ...process.env };
|
|
@@ -1900,22 +1900,22 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1900
1900
|
break;
|
|
1901
1901
|
}
|
|
1902
1902
|
const projectType = action.data.project_type ?? "base";
|
|
1903
|
-
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(
|
|
1903
|
+
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join18(action.targetDir, rel)));
|
|
1904
1904
|
if (missing.length) {
|
|
1905
1905
|
errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
|
|
1906
1906
|
break;
|
|
1907
1907
|
}
|
|
1908
1908
|
if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
|
|
1909
1909
|
} else if (action.kind === "project.write-manifest") {
|
|
1910
|
-
if (action.expectedContent !== void 0 && (!existsSync11(action.path) ||
|
|
1910
|
+
if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync17(action.path, "utf8") !== action.expectedContent)) {
|
|
1911
1911
|
errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
|
|
1912
1912
|
break;
|
|
1913
1913
|
}
|
|
1914
|
-
|
|
1914
|
+
mkdirSync8(dirname14(action.path), { recursive: true });
|
|
1915
1915
|
let value = action.manifest;
|
|
1916
1916
|
if (existsSync11(action.path)) {
|
|
1917
1917
|
try {
|
|
1918
|
-
const currentValue = JSON.parse(
|
|
1918
|
+
const currentValue = JSON.parse(readFileSync17(action.path, "utf8"));
|
|
1919
1919
|
if (isRecord3(currentValue)) {
|
|
1920
1920
|
const currentNotebook = isRecord3(currentValue.notebook) ? currentValue.notebook : {};
|
|
1921
1921
|
const desiredNotebook = isRecord3(value.notebook) ? value.notebook : {};
|
|
@@ -1943,9 +1943,9 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1943
1943
|
value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
|
|
1944
1944
|
const next = `${JSON.stringify(value, null, 2)}
|
|
1945
1945
|
`;
|
|
1946
|
-
const current = existsSync11(action.path) ?
|
|
1946
|
+
const current = existsSync11(action.path) ? readFileSync17(action.path, "utf8") : void 0;
|
|
1947
1947
|
if (current !== next) {
|
|
1948
|
-
|
|
1948
|
+
writeFileSync10(action.path, next, "utf8");
|
|
1949
1949
|
changedFiles.push(action.path);
|
|
1950
1950
|
}
|
|
1951
1951
|
changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
|
|
@@ -2128,7 +2128,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
|
|
|
2128
2128
|
} else if (!statSync5(project.repo_path).isDirectory()) {
|
|
2129
2129
|
issues.push({ level: "error", slug: projectSlug, message: `repo_path is not a directory: ${project.repo_path}` });
|
|
2130
2130
|
} else {
|
|
2131
|
-
const manifestPath =
|
|
2131
|
+
const manifestPath = join18(project.repo_path, ".project.json");
|
|
2132
2132
|
if (!existsSync11(manifestPath)) issues.push({ level: "warn", slug: projectSlug, message: ".project.json is missing" });
|
|
2133
2133
|
}
|
|
2134
2134
|
for (const artifact of project.source_artifacts) {
|
|
@@ -2145,7 +2145,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
|
|
|
2145
2145
|
};
|
|
2146
2146
|
}
|
|
2147
2147
|
function buildCommonProjectCopierAction(input) {
|
|
2148
|
-
const templateDir =
|
|
2148
|
+
const templateDir = join18(input.pjanglerRoot, "templates", "commonproject");
|
|
2149
2149
|
const data = {
|
|
2150
2150
|
project_name: input.projectName,
|
|
2151
2151
|
project_description: input.projectDescription ?? "",
|
|
@@ -2176,7 +2176,7 @@ function buildCommonProjectCopierAction(input) {
|
|
|
2176
2176
|
function resolvePjanglerRoot() {
|
|
2177
2177
|
let dir = dirname14(fileURLToPath2(import.meta.url));
|
|
2178
2178
|
while (dir !== dirname14(dir)) {
|
|
2179
|
-
if (existsSync11(
|
|
2179
|
+
if (existsSync11(join18(dir, "package.json")) && existsSync11(join18(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
2180
2180
|
dir = dirname14(dir);
|
|
2181
2181
|
}
|
|
2182
2182
|
return resolve11(process.cwd());
|
|
@@ -2253,7 +2253,7 @@ function validateProjectRecord(project, key) {
|
|
|
2253
2253
|
}
|
|
2254
2254
|
function expandHome(path) {
|
|
2255
2255
|
if (path === "~") return homedir7();
|
|
2256
|
-
if (path.startsWith("~/")) return
|
|
2256
|
+
if (path.startsWith("~/")) return join18(homedir7(), path.slice(2));
|
|
2257
2257
|
return path;
|
|
2258
2258
|
}
|
|
2259
2259
|
function isRecord3(value) {
|
|
@@ -2280,7 +2280,7 @@ var init_project = __esm({
|
|
|
2280
2280
|
"obsidian-plugin": ["typescript", "javascript"]
|
|
2281
2281
|
};
|
|
2282
2282
|
PROJECT_TYPE_SENTINELS = {
|
|
2283
|
-
"obsidian-plugin": ["manifest.json",
|
|
2283
|
+
"obsidian-plugin": ["manifest.json", join18("src", "main.ts")]
|
|
2284
2284
|
};
|
|
2285
2285
|
BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
|
|
2286
2286
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
@@ -2289,8 +2289,8 @@ var init_project = __esm({
|
|
|
2289
2289
|
IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
|
|
2290
2290
|
DEFAULT_SOURCE_SKILL_ROOTS = [
|
|
2291
2291
|
"/home/delorenj/code/skillex/all-skills",
|
|
2292
|
-
|
|
2293
|
-
|
|
2292
|
+
join18(homedir7(), ".agents", "skills"),
|
|
2293
|
+
join18(homedir7(), ".codex", "skills")
|
|
2294
2294
|
];
|
|
2295
2295
|
PROJECT_REGISTRY_OWNED_KEYS = [
|
|
2296
2296
|
"name",
|
|
@@ -2322,7 +2322,7 @@ var init_project = __esm({
|
|
|
2322
2322
|
import { randomUUID } from "node:crypto";
|
|
2323
2323
|
import { isDeepStrictEqual } from "node:util";
|
|
2324
2324
|
import { isIP as isIP2 } from "node:net";
|
|
2325
|
-
import { closeSync as
|
|
2325
|
+
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
2326
|
import { dirname as dirname15, resolve as resolve12 } from "node:path";
|
|
2327
2327
|
function isRecord4(value) {
|
|
2328
2328
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2336,7 +2336,7 @@ function readManifest(repoPath) {
|
|
|
2336
2336
|
if (!existsSync12(path)) return null;
|
|
2337
2337
|
let parsed;
|
|
2338
2338
|
try {
|
|
2339
|
-
parsed = JSON.parse(
|
|
2339
|
+
parsed = JSON.parse(readFileSync18(path, "utf8"));
|
|
2340
2340
|
} catch {
|
|
2341
2341
|
throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
|
|
2342
2342
|
}
|
|
@@ -2578,7 +2578,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2578
2578
|
const changed = [];
|
|
2579
2579
|
const manifestPath = resolve12(resolved.project.repo_path, ".project.json");
|
|
2580
2580
|
if (isRegistryServiceLocation(resolved.registry_path)) {
|
|
2581
|
-
const before = existsSync12(manifestPath) ?
|
|
2581
|
+
const before = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
|
|
2582
2582
|
const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
|
|
2583
2583
|
const nextProject = {
|
|
2584
2584
|
...resolved.project,
|
|
@@ -2590,10 +2590,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2590
2590
|
resolved.registry = loadProjectRegistry(resolved.registry_path);
|
|
2591
2591
|
resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
|
|
2592
2592
|
resolved.manifest = readManifest(nextProject.repo_path);
|
|
2593
|
-
const after = existsSync12(manifestPath) ?
|
|
2593
|
+
const after = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
|
|
2594
2594
|
return before === after ? [] : [manifestPath];
|
|
2595
2595
|
}
|
|
2596
|
-
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(
|
|
2596
|
+
const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync18(manifestPath, "utf8")) : {};
|
|
2597
2597
|
if (!isRecord4(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
|
|
2598
2598
|
validateManifestNotebookSurface(manifestRaw.notebook);
|
|
2599
2599
|
const manifestNotebook2 = isRecord4(manifestRaw.notebook) ? manifestRaw.notebook : {};
|
|
@@ -2626,29 +2626,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2626
2626
|
};
|
|
2627
2627
|
const manifestText = `${JSON.stringify(manifestNext, null, 2)}
|
|
2628
2628
|
`;
|
|
2629
|
-
if (!existsSync12(manifestPath) ||
|
|
2629
|
+
if (!existsSync12(manifestPath) || readFileSync18(manifestPath, "utf8") !== manifestText) {
|
|
2630
2630
|
let mode = 420;
|
|
2631
2631
|
if (existsSync12(manifestPath)) {
|
|
2632
|
-
const current2 =
|
|
2632
|
+
const current2 = lstatSync13(manifestPath);
|
|
2633
2633
|
if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
|
|
2634
2634
|
mode = current2.mode & 511;
|
|
2635
2635
|
}
|
|
2636
2636
|
const temp = resolve12(dirname15(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
|
|
2637
|
-
const fd =
|
|
2637
|
+
const fd = openSync4(temp, "wx", mode);
|
|
2638
2638
|
try {
|
|
2639
|
-
|
|
2640
|
-
|
|
2639
|
+
writeFileSync11(fd, manifestText, "utf8");
|
|
2640
|
+
fchmodSync3(fd, mode);
|
|
2641
2641
|
fsyncSync2(fd);
|
|
2642
2642
|
} finally {
|
|
2643
|
-
|
|
2643
|
+
closeSync4(fd);
|
|
2644
2644
|
}
|
|
2645
2645
|
renameSync6(temp, manifestPath);
|
|
2646
2646
|
try {
|
|
2647
|
-
const directory =
|
|
2647
|
+
const directory = openSync4(dirname15(manifestPath), "r");
|
|
2648
2648
|
try {
|
|
2649
2649
|
fsyncSync2(directory);
|
|
2650
2650
|
} finally {
|
|
2651
|
-
|
|
2651
|
+
closeSync4(directory);
|
|
2652
2652
|
}
|
|
2653
2653
|
} catch {
|
|
2654
2654
|
}
|
|
@@ -2657,9 +2657,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
|
|
|
2657
2657
|
const current = resolved.project.notebook;
|
|
2658
2658
|
resolved.project.notebook = { ...isRecord4(current) ? current : { state: "planned" }, ...binding };
|
|
2659
2659
|
const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
|
|
2660
|
-
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2660
|
+
const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
|
|
2661
2661
|
saveProjectRegistry(resolved.registry, resolved.registry_path);
|
|
2662
|
-
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ?
|
|
2662
|
+
const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
|
|
2663
2663
|
if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
|
|
2664
2664
|
resolved.manifest = manifestNext;
|
|
2665
2665
|
return changed;
|
|
@@ -2692,7 +2692,7 @@ var init_config = __esm({
|
|
|
2692
2692
|
});
|
|
2693
2693
|
|
|
2694
2694
|
// src/notebook/notes.ts
|
|
2695
|
-
import { createHash as
|
|
2695
|
+
import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
|
|
2696
2696
|
function canonicalJson2(value) {
|
|
2697
2697
|
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
2698
|
if (value === null || typeof value !== "object") {
|
|
@@ -2705,7 +2705,7 @@ function canonicalJson2(value) {
|
|
|
2705
2705
|
return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
|
|
2706
2706
|
}
|
|
2707
2707
|
function sha256Hex(value) {
|
|
2708
|
-
return
|
|
2708
|
+
return createHash10("sha256").update(value).digest("hex");
|
|
2709
2709
|
}
|
|
2710
2710
|
function base64UrlEncode(value) {
|
|
2711
2711
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
@@ -3256,32 +3256,32 @@ var init_remote_mutation_schema = __esm({
|
|
|
3256
3256
|
});
|
|
3257
3257
|
|
|
3258
3258
|
// src/notebook/state.ts
|
|
3259
|
-
import { createHash as
|
|
3259
|
+
import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
|
|
3260
3260
|
import {
|
|
3261
|
-
closeSync as
|
|
3262
|
-
constants as
|
|
3261
|
+
closeSync as closeSync5,
|
|
3262
|
+
constants as constants4,
|
|
3263
3263
|
existsSync as existsSync13,
|
|
3264
|
-
fchmodSync as
|
|
3265
|
-
fstatSync,
|
|
3264
|
+
fchmodSync as fchmodSync4,
|
|
3265
|
+
fstatSync as fstatSync2,
|
|
3266
3266
|
fsyncSync as fsyncSync3,
|
|
3267
|
-
lstatSync as
|
|
3268
|
-
mkdirSync as
|
|
3269
|
-
openSync as
|
|
3267
|
+
lstatSync as lstatSync14,
|
|
3268
|
+
mkdirSync as mkdirSync9,
|
|
3269
|
+
openSync as openSync5,
|
|
3270
3270
|
readSync,
|
|
3271
|
-
readdirSync as
|
|
3271
|
+
readdirSync as readdirSync10,
|
|
3272
3272
|
renameSync as renameSync7,
|
|
3273
|
-
unlinkSync as
|
|
3274
|
-
writeFileSync as
|
|
3273
|
+
unlinkSync as unlinkSync8,
|
|
3274
|
+
writeFileSync as writeFileSync12
|
|
3275
3275
|
} from "node:fs";
|
|
3276
3276
|
import { homedir as homedir8 } from "node:os";
|
|
3277
|
-
import { basename as
|
|
3277
|
+
import { basename as basename10, dirname as dirname16, join as join19, parse, relative as relative12, resolve as resolve13, sep as sep4 } from "node:path";
|
|
3278
3278
|
function notebookStateRoot(env2 = process.env) {
|
|
3279
|
-
const base = env2.XDG_STATE_HOME ||
|
|
3279
|
+
const base = env2.XDG_STATE_HOME || join19(env2.HOME || homedir8(), ".local", "state");
|
|
3280
3280
|
return resolve13(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
|
|
3281
3281
|
}
|
|
3282
3282
|
function deriveSessionKey(projectSlug, client, clientSessionId) {
|
|
3283
3283
|
if (!projectSlug || !client || !clientSessionId) throw new NotebookError("INVALID_INPUT", "project slug, client, and client session id are required");
|
|
3284
|
-
return
|
|
3284
|
+
return createHash11("sha256").update(`pjangler-session-v1\0${projectSlug}\0${client}\0${clientSessionId}`, "utf8").digest("hex");
|
|
3285
3285
|
}
|
|
3286
3286
|
function deriveReceiptId(sessionKey) {
|
|
3287
3287
|
assertDigest(sessionKey, "session key");
|
|
@@ -3295,7 +3295,7 @@ function assertDigest(value, label) {
|
|
|
3295
3295
|
}
|
|
3296
3296
|
function projectStateDir(root, projectSlug) {
|
|
3297
3297
|
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
|
|
3298
|
+
return join19(resolve13(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
|
|
3299
3299
|
}
|
|
3300
3300
|
function assertContained(root, candidate) {
|
|
3301
3301
|
const rel = relative12(resolve13(root), resolve13(candidate));
|
|
@@ -3308,33 +3308,33 @@ function openPinnedDirectory(path, root, create) {
|
|
|
3308
3308
|
assertContained(absoluteRoot, absolute);
|
|
3309
3309
|
if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
|
|
3310
3310
|
const parsed = parse(absolute);
|
|
3311
|
-
let fd =
|
|
3311
|
+
let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
|
|
3312
3312
|
let cursor = parsed.root;
|
|
3313
3313
|
try {
|
|
3314
3314
|
for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
|
|
3315
3315
|
const child = `/proc/self/fd/${fd}/${part}`;
|
|
3316
|
-
cursor =
|
|
3316
|
+
cursor = join19(cursor, part);
|
|
3317
3317
|
let childFd;
|
|
3318
3318
|
try {
|
|
3319
|
-
childFd =
|
|
3319
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
3320
3320
|
} catch (error) {
|
|
3321
3321
|
if (!create || error.code !== "ENOENT") throw error;
|
|
3322
3322
|
try {
|
|
3323
|
-
|
|
3323
|
+
mkdirSync9(child, { mode: 448 });
|
|
3324
3324
|
} catch (mkdirError) {
|
|
3325
3325
|
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
3326
3326
|
}
|
|
3327
|
-
childFd =
|
|
3327
|
+
childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
|
|
3328
3328
|
}
|
|
3329
|
-
|
|
3329
|
+
closeSync5(fd);
|
|
3330
3330
|
fd = childFd;
|
|
3331
|
-
const stat =
|
|
3331
|
+
const stat = fstatSync2(fd);
|
|
3332
3332
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
|
|
3333
3333
|
if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
|
|
3334
3334
|
}
|
|
3335
3335
|
return fd;
|
|
3336
3336
|
} catch (error) {
|
|
3337
|
-
|
|
3337
|
+
closeSync5(fd);
|
|
3338
3338
|
if (error.code === "ELOOP" || error.code === "ENOTDIR") {
|
|
3339
3339
|
throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
|
|
3340
3340
|
}
|
|
@@ -3342,7 +3342,7 @@ function openPinnedDirectory(path, root, create) {
|
|
|
3342
3342
|
}
|
|
3343
3343
|
}
|
|
3344
3344
|
function pinnedLeaf(parentFd, path) {
|
|
3345
|
-
return `/proc/self/fd/${parentFd}/${
|
|
3345
|
+
return `/proc/self/fd/${parentFd}/${basename10(path)}`;
|
|
3346
3346
|
}
|
|
3347
3347
|
function assertOwned(stat) {
|
|
3348
3348
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
@@ -3355,13 +3355,13 @@ function ensureDirectory(path, root) {
|
|
|
3355
3355
|
assertContained(absoluteRoot, absolute);
|
|
3356
3356
|
const fd = openPinnedDirectory(absolute, absoluteRoot, true);
|
|
3357
3357
|
try {
|
|
3358
|
-
const stat =
|
|
3358
|
+
const stat = fstatSync2(fd);
|
|
3359
3359
|
if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
|
|
3360
3360
|
assertOwned(stat);
|
|
3361
|
-
|
|
3361
|
+
fchmodSync4(fd, 448);
|
|
3362
3362
|
fsyncSync3(fd);
|
|
3363
3363
|
} finally {
|
|
3364
|
-
|
|
3364
|
+
closeSync5(fd);
|
|
3365
3365
|
}
|
|
3366
3366
|
}
|
|
3367
3367
|
function notebookStatePaths(root, projectSlug) {
|
|
@@ -3370,18 +3370,18 @@ function notebookStatePaths(root, projectSlug) {
|
|
|
3370
3370
|
return {
|
|
3371
3371
|
root: absoluteRoot,
|
|
3372
3372
|
project,
|
|
3373
|
-
baselines:
|
|
3374
|
-
claims:
|
|
3375
|
-
receipts:
|
|
3376
|
-
refusals:
|
|
3377
|
-
journals:
|
|
3378
|
-
locks:
|
|
3373
|
+
baselines: join19(project, "baselines"),
|
|
3374
|
+
claims: join19(project, "claims"),
|
|
3375
|
+
receipts: join19(project, "receipts"),
|
|
3376
|
+
refusals: join19(project, "refusals"),
|
|
3377
|
+
journals: join19(project, "journals"),
|
|
3378
|
+
locks: join19(project, "locks")
|
|
3379
3379
|
};
|
|
3380
3380
|
}
|
|
3381
3381
|
function ensureNotebookState(root, projectSlug) {
|
|
3382
3382
|
const paths = notebookStatePaths(root, projectSlug);
|
|
3383
3383
|
const { root: absoluteRoot, project } = paths;
|
|
3384
|
-
for (const path of [absoluteRoot,
|
|
3384
|
+
for (const path of [absoluteRoot, join19(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
|
|
3385
3385
|
ensureDirectory(path, absoluteRoot);
|
|
3386
3386
|
}
|
|
3387
3387
|
return paths;
|
|
@@ -3397,15 +3397,15 @@ function fsyncDirectory2(path) {
|
|
|
3397
3397
|
fsyncSync3(fd);
|
|
3398
3398
|
} catch {
|
|
3399
3399
|
} finally {
|
|
3400
|
-
if (fd !== void 0)
|
|
3400
|
+
if (fd !== void 0) closeSync5(fd);
|
|
3401
3401
|
}
|
|
3402
3402
|
}
|
|
3403
3403
|
function readStateDirectory(path, root) {
|
|
3404
3404
|
const fd = openPinnedDirectory(path, root, false);
|
|
3405
3405
|
try {
|
|
3406
|
-
return
|
|
3406
|
+
return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
|
|
3407
3407
|
} finally {
|
|
3408
|
-
|
|
3408
|
+
closeSync5(fd);
|
|
3409
3409
|
}
|
|
3410
3410
|
}
|
|
3411
3411
|
function readNotebookStateDirectory(path, root) {
|
|
@@ -3419,22 +3419,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
|
|
|
3419
3419
|
let fileFd;
|
|
3420
3420
|
try {
|
|
3421
3421
|
try {
|
|
3422
|
-
fileFd =
|
|
3422
|
+
fileFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3423
3423
|
} catch (error) {
|
|
3424
3424
|
if (allowMissing && error.code === "ENOENT") return false;
|
|
3425
3425
|
throw error;
|
|
3426
3426
|
}
|
|
3427
|
-
const stat =
|
|
3427
|
+
const stat = fstatSync2(fileFd);
|
|
3428
3428
|
if (!stat.isFile() || (stat.mode & 511) !== 384) {
|
|
3429
3429
|
throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
|
|
3430
3430
|
}
|
|
3431
3431
|
assertOwned(stat);
|
|
3432
|
-
|
|
3432
|
+
unlinkSync8(target);
|
|
3433
3433
|
fsyncSync3(parentFd);
|
|
3434
3434
|
return true;
|
|
3435
3435
|
} finally {
|
|
3436
|
-
if (fileFd !== void 0)
|
|
3437
|
-
|
|
3436
|
+
if (fileFd !== void 0) closeSync5(fileFd);
|
|
3437
|
+
closeSync5(parentFd);
|
|
3438
3438
|
}
|
|
3439
3439
|
}
|
|
3440
3440
|
function renameStateFile(source, target, root) {
|
|
@@ -3444,12 +3444,12 @@ function renameStateFile(source, target, root) {
|
|
|
3444
3444
|
const targetPath = pinnedLeaf(parentFd, target);
|
|
3445
3445
|
let sourceFd;
|
|
3446
3446
|
try {
|
|
3447
|
-
sourceFd =
|
|
3448
|
-
const stat =
|
|
3447
|
+
sourceFd = openSync5(sourcePath, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3448
|
+
const stat = fstatSync2(sourceFd);
|
|
3449
3449
|
if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
|
|
3450
3450
|
assertOwned(stat);
|
|
3451
3451
|
try {
|
|
3452
|
-
|
|
3452
|
+
lstatSync14(targetPath);
|
|
3453
3453
|
throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
|
|
3454
3454
|
} catch (error) {
|
|
3455
3455
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -3457,8 +3457,8 @@ function renameStateFile(source, target, root) {
|
|
|
3457
3457
|
renameSync7(sourcePath, targetPath);
|
|
3458
3458
|
fsyncSync3(parentFd);
|
|
3459
3459
|
} finally {
|
|
3460
|
-
if (sourceFd !== void 0)
|
|
3461
|
-
|
|
3460
|
+
if (sourceFd !== void 0) closeSync5(sourceFd);
|
|
3461
|
+
closeSync5(parentFd);
|
|
3462
3462
|
}
|
|
3463
3463
|
}
|
|
3464
3464
|
function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
@@ -3467,34 +3467,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
3467
3467
|
const text2 = jsonLine(value);
|
|
3468
3468
|
const parentFd = openPinnedDirectory(dirname16(path), root, false);
|
|
3469
3469
|
const target = pinnedLeaf(parentFd, path);
|
|
3470
|
-
const tempName = `.${
|
|
3470
|
+
const tempName = `.${basename10(path)}.${process.pid}.${randomUUID3()}.tmp`;
|
|
3471
3471
|
const temp = `/proc/self/fd/${parentFd}/${tempName}`;
|
|
3472
3472
|
let existingIdentity = null;
|
|
3473
3473
|
try {
|
|
3474
3474
|
afterParentPinned?.();
|
|
3475
3475
|
try {
|
|
3476
|
-
const existingFd =
|
|
3476
|
+
const existingFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3477
3477
|
try {
|
|
3478
|
-
const existing =
|
|
3478
|
+
const existing = fstatSync2(existingFd);
|
|
3479
3479
|
if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
|
|
3480
3480
|
assertOwned(existing);
|
|
3481
3481
|
if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
|
|
3482
3482
|
existingIdentity = { dev: existing.dev, ino: existing.ino };
|
|
3483
3483
|
} finally {
|
|
3484
|
-
|
|
3484
|
+
closeSync5(existingFd);
|
|
3485
3485
|
}
|
|
3486
3486
|
} catch (error) {
|
|
3487
3487
|
if (error.code !== "ENOENT") throw error;
|
|
3488
3488
|
}
|
|
3489
|
-
const fd =
|
|
3489
|
+
const fd = openSync5(temp, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
|
|
3490
3490
|
try {
|
|
3491
|
-
|
|
3491
|
+
writeFileSync12(fd, text2, "utf8");
|
|
3492
3492
|
fsyncSync3(fd);
|
|
3493
3493
|
} finally {
|
|
3494
|
-
|
|
3494
|
+
closeSync5(fd);
|
|
3495
3495
|
}
|
|
3496
3496
|
try {
|
|
3497
|
-
const current =
|
|
3497
|
+
const current = lstatSync14(target);
|
|
3498
3498
|
if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
|
|
3499
3499
|
throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
|
|
3500
3500
|
}
|
|
@@ -3505,12 +3505,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
|
|
|
3505
3505
|
fsyncSync3(parentFd);
|
|
3506
3506
|
} catch (error) {
|
|
3507
3507
|
try {
|
|
3508
|
-
|
|
3508
|
+
unlinkSync8(temp);
|
|
3509
3509
|
} catch {
|
|
3510
3510
|
}
|
|
3511
3511
|
throw error;
|
|
3512
3512
|
} finally {
|
|
3513
|
-
|
|
3513
|
+
closeSync5(parentFd);
|
|
3514
3514
|
}
|
|
3515
3515
|
return Buffer.byteLength(text2, "utf8");
|
|
3516
3516
|
}
|
|
@@ -3520,22 +3520,22 @@ function exclusiveWrite(path, text2, root) {
|
|
|
3520
3520
|
const target = pinnedLeaf(parentFd, path);
|
|
3521
3521
|
let fd;
|
|
3522
3522
|
try {
|
|
3523
|
-
fd =
|
|
3523
|
+
fd = openSync5(target, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
|
|
3524
3524
|
} catch (error) {
|
|
3525
|
-
|
|
3525
|
+
closeSync5(parentFd);
|
|
3526
3526
|
if (error.code === "EEXIST") return false;
|
|
3527
3527
|
throw error;
|
|
3528
3528
|
}
|
|
3529
3529
|
try {
|
|
3530
|
-
|
|
3530
|
+
writeFileSync12(fd, text2, "utf8");
|
|
3531
3531
|
fsyncSync3(fd);
|
|
3532
3532
|
} finally {
|
|
3533
|
-
|
|
3533
|
+
closeSync5(fd);
|
|
3534
3534
|
}
|
|
3535
3535
|
try {
|
|
3536
3536
|
fsyncSync3(parentFd);
|
|
3537
3537
|
} finally {
|
|
3538
|
-
|
|
3538
|
+
closeSync5(parentFd);
|
|
3539
3539
|
}
|
|
3540
3540
|
return true;
|
|
3541
3541
|
}
|
|
@@ -3679,13 +3679,13 @@ function safeReadJson(path, maxBytes) {
|
|
|
3679
3679
|
}
|
|
3680
3680
|
let fd;
|
|
3681
3681
|
try {
|
|
3682
|
-
fd =
|
|
3682
|
+
fd = openSync5(pinnedLeaf(parentFd, path), constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
3683
3683
|
} catch (error) {
|
|
3684
|
-
|
|
3684
|
+
closeSync5(parentFd);
|
|
3685
3685
|
return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
|
|
3686
3686
|
}
|
|
3687
3687
|
try {
|
|
3688
|
-
const stat =
|
|
3688
|
+
const stat = fstatSync2(fd);
|
|
3689
3689
|
if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
|
|
3690
3690
|
if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
|
|
3691
3691
|
try {
|
|
@@ -3705,14 +3705,14 @@ function safeReadJson(path, maxBytes) {
|
|
|
3705
3705
|
if (total > maxBytes) return { reason: "oversize", bytes: total };
|
|
3706
3706
|
chunks.push(chunk.subarray(0, count));
|
|
3707
3707
|
}
|
|
3708
|
-
const after =
|
|
3708
|
+
const after = fstatSync2(fd);
|
|
3709
3709
|
if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
|
|
3710
3710
|
return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
|
|
3711
3711
|
} catch (error) {
|
|
3712
3712
|
return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
|
|
3713
3713
|
} finally {
|
|
3714
|
-
|
|
3715
|
-
|
|
3714
|
+
closeSync5(fd);
|
|
3715
|
+
closeSync5(parentFd);
|
|
3716
3716
|
}
|
|
3717
3717
|
}
|
|
3718
3718
|
function safeEntryId(kind, name) {
|
|
@@ -3729,7 +3729,7 @@ function baselineReceiptByteCeiling(limits) {
|
|
|
3729
3729
|
function createSessionBaseline(root, input) {
|
|
3730
3730
|
assertDigest(input.session_key, "session key");
|
|
3731
3731
|
const paths = ensureNotebookState(root, input.project_slug);
|
|
3732
|
-
const path =
|
|
3732
|
+
const path = join19(paths.baselines, `${input.session_key}.json`);
|
|
3733
3733
|
const maxBytes = baselineReceiptByteCeiling(input.limits);
|
|
3734
3734
|
const existing = readBaseline(path, maxBytes);
|
|
3735
3735
|
if (existing) return { baseline: existing, created: false };
|
|
@@ -3770,7 +3770,7 @@ function createSessionBaseline(root, input) {
|
|
|
3770
3770
|
function createOverviewClaim(root, input) {
|
|
3771
3771
|
assertDigest(input.session_key, "session key");
|
|
3772
3772
|
const paths = ensureNotebookState(root, input.project_slug);
|
|
3773
|
-
const path =
|
|
3773
|
+
const path = join19(paths.claims, `${input.session_key}.overview`);
|
|
3774
3774
|
if (existsSync13(path)) {
|
|
3775
3775
|
const read = safeReadJson(path, 65536);
|
|
3776
3776
|
const existing = read.value === void 0 ? null : parseClaim(read.value);
|
|
@@ -3789,7 +3789,7 @@ function createOverviewClaim(root, input) {
|
|
|
3789
3789
|
function readOverviewClaim(root, projectSlug, sessionKey) {
|
|
3790
3790
|
assertDigest(sessionKey, "session key");
|
|
3791
3791
|
const paths = notebookStatePaths(root, projectSlug);
|
|
3792
|
-
const path =
|
|
3792
|
+
const path = join19(paths.claims, `${sessionKey}.overview`);
|
|
3793
3793
|
if (!existsSync13(path)) return null;
|
|
3794
3794
|
const read = safeReadJson(path, 65536);
|
|
3795
3795
|
return read.value === void 0 ? null : parseClaim(read.value);
|
|
@@ -3797,10 +3797,10 @@ function readOverviewClaim(root, projectSlug, sessionKey) {
|
|
|
3797
3797
|
function readSessionBaseline(root, projectSlug, sessionKey, limits) {
|
|
3798
3798
|
assertDigest(sessionKey, "session key");
|
|
3799
3799
|
const paths = ensureNotebookState(root, projectSlug);
|
|
3800
|
-
return readBaseline(
|
|
3800
|
+
return readBaseline(join19(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
|
|
3801
3801
|
}
|
|
3802
3802
|
function acquireLock(paths, maxWaitMs) {
|
|
3803
|
-
const lock =
|
|
3803
|
+
const lock = join19(paths.locks, "admission.lock");
|
|
3804
3804
|
const deadline = Date.now() + Math.max(1, maxWaitMs);
|
|
3805
3805
|
const token = randomUUID3();
|
|
3806
3806
|
const record3 = () => ({
|
|
@@ -3830,7 +3830,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3830
3830
|
throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
|
|
3831
3831
|
}
|
|
3832
3832
|
if (Date.now() >= expiresAt) {
|
|
3833
|
-
const recovery =
|
|
3833
|
+
const recovery = join19(paths.locks, `recovery-${heldToken}.lock`);
|
|
3834
3834
|
const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
|
|
3835
3835
|
if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
|
|
3836
3836
|
try {
|
|
@@ -3838,7 +3838,7 @@ function acquireLock(paths, maxWaitMs) {
|
|
|
3838
3838
|
const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
|
|
3839
3839
|
const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
|
|
3840
3840
|
if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
|
|
3841
|
-
const recovered =
|
|
3841
|
+
const recovered = join19(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
|
|
3842
3842
|
renameStateFile(lock, recovered, paths.root);
|
|
3843
3843
|
unlinkStateFile(recovered, paths.root);
|
|
3844
3844
|
}
|
|
@@ -3918,7 +3918,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3918
3918
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3919
3919
|
continue;
|
|
3920
3920
|
}
|
|
3921
|
-
const read = safeReadJson(
|
|
3921
|
+
const read = safeReadJson(join19(specification.dir, entry.name), specification.maxBytes);
|
|
3922
3922
|
if (read.reason || read.value === void 0) {
|
|
3923
3923
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3924
3924
|
continue;
|
|
@@ -3943,7 +3943,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3943
3943
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3944
3944
|
continue;
|
|
3945
3945
|
}
|
|
3946
|
-
const read = safeReadJson(
|
|
3946
|
+
const read = safeReadJson(join19(paths.journals, entry.name), limits.receipt_max_bytes);
|
|
3947
3947
|
if (read.reason || read.value === void 0) {
|
|
3948
3948
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3949
3949
|
continue;
|
|
@@ -3972,7 +3972,7 @@ function scanAuxiliaryState(paths, limits) {
|
|
|
3972
3972
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
|
|
3973
3973
|
continue;
|
|
3974
3974
|
}
|
|
3975
|
-
const read = safeReadJson(
|
|
3975
|
+
const read = safeReadJson(join19(paths.locks, entry.name), 8192);
|
|
3976
3976
|
if (read.reason || read.value === void 0) {
|
|
3977
3977
|
addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
|
|
3978
3978
|
continue;
|
|
@@ -4011,7 +4011,7 @@ function scanReceipts(paths, limits) {
|
|
|
4011
4011
|
}
|
|
4012
4012
|
for (const entry of entries) {
|
|
4013
4013
|
const entryId = safeEntryId("receipts", entry.name);
|
|
4014
|
-
const path =
|
|
4014
|
+
const path = join19(paths.receipts, entry.name);
|
|
4015
4015
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
|
|
4016
4016
|
addIntegrity({ entry_id: entryId, reason: "non-regular" });
|
|
4017
4017
|
continue;
|
|
@@ -4041,7 +4041,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
|
|
|
4041
4041
|
if (!existsSync13(paths.baselines)) return { current, stale };
|
|
4042
4042
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
4043
4043
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
4044
|
-
const baseline = readBaseline(
|
|
4044
|
+
const baseline = readBaseline(join19(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
|
|
4045
4045
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
4046
4046
|
const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
|
|
4047
4047
|
if (nowMs >= expires) stale += 1;
|
|
@@ -4055,7 +4055,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
|
|
|
4055
4055
|
const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
|
|
4056
4056
|
for (const entry of entries) {
|
|
4057
4057
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
4058
|
-
const read = safeReadJson(
|
|
4058
|
+
const read = safeReadJson(join19(paths.refusals, entry.name), limits.receipt_max_bytes);
|
|
4059
4059
|
const marker = read.value === void 0 ? null : parseRefusal(read.value);
|
|
4060
4060
|
if (!marker || `${marker.session_key}.json` !== entry.name) continue;
|
|
4061
4061
|
if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
@@ -4151,7 +4151,7 @@ function recoveryActions(receiptId, repoPath) {
|
|
|
4151
4151
|
];
|
|
4152
4152
|
}
|
|
4153
4153
|
function removeValidRefusal(paths, sessionKey, limits) {
|
|
4154
|
-
const path =
|
|
4154
|
+
const path = join19(paths.refusals, `${sessionKey}.json`);
|
|
4155
4155
|
if (!existsSync13(path)) return;
|
|
4156
4156
|
const read = safeReadJson(path, limits.receipt_max_bytes);
|
|
4157
4157
|
const marker = read.value === void 0 ? null : parseRefusal(read.value);
|
|
@@ -4165,7 +4165,7 @@ function admitCaptureReceipt(input) {
|
|
|
4165
4165
|
const release = acquireLock(paths, Math.min(100, input.limits.hook_session_end_timeout_ms));
|
|
4166
4166
|
try {
|
|
4167
4167
|
const receiptId = deriveReceiptId(input.sessionKey);
|
|
4168
|
-
const receiptPath =
|
|
4168
|
+
const receiptPath = join19(paths.receipts, `${receiptId}.json`);
|
|
4169
4169
|
if (existsSync13(receiptPath)) {
|
|
4170
4170
|
const read = safeReadJson(receiptPath, input.limits.receipt_max_bytes);
|
|
4171
4171
|
const receipt = read.value === void 0 ? null : parseReceipt(read.value);
|
|
@@ -4177,15 +4177,15 @@ function admitCaptureReceipt(input) {
|
|
|
4177
4177
|
const scanBefore = scanReceipts(paths, input.limits);
|
|
4178
4178
|
const auxiliaryBefore = scanAuxiliaryState(paths, input.limits);
|
|
4179
4179
|
const referencedBefore = /* @__PURE__ */ new Set([...scanBefore.referencedSessions, ...auxiliaryBefore.referencedSessions]);
|
|
4180
|
-
const baselinePath =
|
|
4180
|
+
const baselinePath = join19(paths.baselines, `${input.sessionKey}.json`);
|
|
4181
4181
|
let baseline = readBaseline(baselinePath, baselineReceiptByteCeiling(input.limits));
|
|
4182
4182
|
if (baseline) {
|
|
4183
4183
|
const expired = now.getTime() >= Date.parse(baseline.created_at) + input.limits.receiptless_session_retention_seconds * 1e3;
|
|
4184
4184
|
if (expired && !referencedBefore.has(input.sessionKey) && scanBefore.integrityCount === 0 && auxiliaryBefore.integrityCount === 0) {
|
|
4185
4185
|
unlinkStateFile(baselinePath, paths.root);
|
|
4186
|
-
const claimPath =
|
|
4186
|
+
const claimPath = join19(paths.claims, `${input.sessionKey}.overview`);
|
|
4187
4187
|
unlinkStateFile(claimPath, paths.root, true);
|
|
4188
|
-
const refusalPath =
|
|
4188
|
+
const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
|
|
4189
4189
|
unlinkStateFile(refusalPath, paths.root, true);
|
|
4190
4190
|
baseline = null;
|
|
4191
4191
|
}
|
|
@@ -4214,7 +4214,7 @@ function admitCaptureReceipt(input) {
|
|
|
4214
4214
|
const byteBlocked = summary.unresolved_bytes + candidate.bytes > input.limits.unresolved_receipt_max_bytes;
|
|
4215
4215
|
if (countBlocked || byteBlocked) {
|
|
4216
4216
|
const actions = recoveryActions(receiptId, input.repoPath);
|
|
4217
|
-
const refusalPath =
|
|
4217
|
+
const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
|
|
4218
4218
|
const oldRead = existsSync13(refusalPath) ? safeReadJson(refusalPath, input.limits.receipt_max_bytes) : void 0;
|
|
4219
4219
|
const old = oldRead?.value === void 0 ? null : parseRefusal(oldRead.value);
|
|
4220
4220
|
const marker = {
|
|
@@ -4283,7 +4283,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
|
|
|
4283
4283
|
function readCaptureReceipt(root, projectSlug, receiptId, limits) {
|
|
4284
4284
|
if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
|
|
4285
4285
|
const paths = notebookStatePaths(root, projectSlug);
|
|
4286
|
-
const path =
|
|
4286
|
+
const path = join19(paths.receipts, `${receiptId}.json`);
|
|
4287
4287
|
if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
|
|
4288
4288
|
const read = safeReadJson(path, limits.receipt_max_bytes);
|
|
4289
4289
|
const receipt = read.value === void 0 ? null : parseReceipt(read.value);
|
|
@@ -4296,7 +4296,7 @@ function writeReceipt(paths, receipt, limit) {
|
|
|
4296
4296
|
const bytes = Buffer.byteLength(text2, "utf8");
|
|
4297
4297
|
if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
|
|
4298
4298
|
if (receipt.serialized_bytes === bytes) {
|
|
4299
|
-
atomicWriteJson(
|
|
4299
|
+
atomicWriteJson(join19(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
|
|
4300
4300
|
return receipt;
|
|
4301
4301
|
}
|
|
4302
4302
|
receipt.serialized_bytes = bytes;
|
|
@@ -4435,25 +4435,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
|
|
|
4435
4435
|
const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
|
|
4436
4436
|
for (const receipt of scan.receipts) {
|
|
4437
4437
|
if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
|
|
4438
|
-
const path =
|
|
4438
|
+
const path = join19(paths.receipts, `${receipt.receipt_id}.json`);
|
|
4439
4439
|
unlinkStateFile(path, paths.root);
|
|
4440
|
-
removed.push(safeEntryId("receipts",
|
|
4440
|
+
removed.push(safeEntryId("receipts", basename10(path)));
|
|
4441
4441
|
}
|
|
4442
4442
|
const remaining = scanReceipts(paths, limits);
|
|
4443
4443
|
const remainingAuxiliary = scanAuxiliaryState(paths, limits);
|
|
4444
4444
|
const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
|
|
4445
4445
|
for (const entry of readStateDirectory(paths.baselines, paths.root)) {
|
|
4446
4446
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
4447
|
-
const path =
|
|
4447
|
+
const path = join19(paths.baselines, entry.name);
|
|
4448
4448
|
const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
|
|
4449
4449
|
if (!baseline || referenced.has(baseline.session_key)) continue;
|
|
4450
4450
|
if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
|
|
4451
4451
|
unlinkStateFile(path, paths.root);
|
|
4452
4452
|
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",
|
|
4453
|
+
const claim = join19(paths.claims, `${baseline.session_key}.overview`);
|
|
4454
|
+
if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename10(claim)));
|
|
4455
|
+
const refusal = join19(paths.refusals, `${baseline.session_key}.json`);
|
|
4456
|
+
if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename10(refusal)));
|
|
4457
4457
|
}
|
|
4458
4458
|
fsyncDirectory2(paths.project);
|
|
4459
4459
|
return removed;
|
|
@@ -4476,7 +4476,7 @@ function currentRetentionPressure(summary) {
|
|
|
4476
4476
|
}
|
|
4477
4477
|
function statePathForReceipt(root, projectSlug, receiptId) {
|
|
4478
4478
|
if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
|
|
4479
|
-
return
|
|
4479
|
+
return join19(projectStateDir(root, projectSlug), "receipts", `${receiptId}.json`);
|
|
4480
4480
|
}
|
|
4481
4481
|
var NOTEBOOK_STATE_VERSION, SESSION_KEY_RE, RECEIPT_ID_RE, GIT_OBJECT_RE, NOTEBOOK_ERROR_CODES, DIRECTORY_OPEN_FLAGS;
|
|
4482
4482
|
var init_state = __esm({
|
|
@@ -4503,7 +4503,7 @@ var init_state = __esm({
|
|
|
4503
4503
|
"REMOTE_PROTOCOL_ERROR",
|
|
4504
4504
|
"INTERNAL_ERROR"
|
|
4505
4505
|
]);
|
|
4506
|
-
DIRECTORY_OPEN_FLAGS =
|
|
4506
|
+
DIRECTORY_OPEN_FLAGS = constants4.O_RDONLY | (constants4.O_DIRECTORY ?? 0) | (constants4.O_NOFOLLOW ?? 0);
|
|
4507
4507
|
}
|
|
4508
4508
|
});
|
|
4509
4509
|
|
|
@@ -4842,8 +4842,8 @@ var init_open_notebook_client = __esm({
|
|
|
4842
4842
|
|
|
4843
4843
|
// src/notebook/git-evidence.ts
|
|
4844
4844
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
4845
|
-
import { closeSync as
|
|
4846
|
-
import { extname, join as
|
|
4845
|
+
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";
|
|
4846
|
+
import { extname, join as join21, relative as relative13, resolve as resolve14, sep as sep5 } from "node:path";
|
|
4847
4847
|
function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
|
|
4848
4848
|
const result2 = spawnSync9("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
|
|
4849
4849
|
return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
|
|
@@ -4921,9 +4921,9 @@ function hasSymlinkComponent(root, candidate) {
|
|
|
4921
4921
|
const rel = relative13(root, candidate);
|
|
4922
4922
|
let cursor = root;
|
|
4923
4923
|
for (const part of rel.split(sep5).filter(Boolean)) {
|
|
4924
|
-
cursor =
|
|
4924
|
+
cursor = join21(cursor, part);
|
|
4925
4925
|
try {
|
|
4926
|
-
if (
|
|
4926
|
+
if (lstatSync15(cursor).isSymbolicLink()) return true;
|
|
4927
4927
|
} catch {
|
|
4928
4928
|
return true;
|
|
4929
4929
|
}
|
|
@@ -4935,12 +4935,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4935
4935
|
if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
|
|
4936
4936
|
let fd;
|
|
4937
4937
|
try {
|
|
4938
|
-
fd =
|
|
4938
|
+
fd = openSync6(safe.candidate, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
|
|
4939
4939
|
} catch {
|
|
4940
4940
|
return { status: "excluded", reason: "not-regular" };
|
|
4941
4941
|
}
|
|
4942
4942
|
try {
|
|
4943
|
-
const before =
|
|
4943
|
+
const before = fstatSync3(fd);
|
|
4944
4944
|
if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
|
|
4945
4945
|
if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4946
4946
|
let physical;
|
|
@@ -4961,7 +4961,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4961
4961
|
if (total > maxBytes) return { status: "excluded", reason: "oversize" };
|
|
4962
4962
|
chunks.push(chunk.subarray(0, count));
|
|
4963
4963
|
}
|
|
4964
|
-
const after =
|
|
4964
|
+
const after = fstatSync3(fd);
|
|
4965
4965
|
if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
|
|
4966
4966
|
const bytes = Buffer.concat(chunks, total);
|
|
4967
4967
|
if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
|
|
@@ -4974,7 +4974,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
|
|
|
4974
4974
|
if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
|
|
4975
4975
|
return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
|
|
4976
4976
|
} finally {
|
|
4977
|
-
|
|
4977
|
+
closeSync6(fd);
|
|
4978
4978
|
}
|
|
4979
4979
|
}
|
|
4980
4980
|
function looksGenerated(path) {
|
|
@@ -5064,10 +5064,10 @@ var init_git_evidence = __esm({
|
|
|
5064
5064
|
|
|
5065
5065
|
// src/notebook/remote-mutation-journal.ts
|
|
5066
5066
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
5067
|
-
import { join as
|
|
5067
|
+
import { join as join22 } from "node:path";
|
|
5068
5068
|
function journalPath(root, projectSlug, operationId) {
|
|
5069
5069
|
if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
|
|
5070
|
-
return
|
|
5070
|
+
return join22(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
|
|
5071
5071
|
}
|
|
5072
5072
|
function remoteMutationJournalPath(root, projectSlug, operationId) {
|
|
5073
5073
|
return journalPath(root, projectSlug, operationId);
|
|
@@ -5087,7 +5087,7 @@ function listRemoteMutationJournals(root, projectSlug) {
|
|
|
5087
5087
|
if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
|
|
5088
5088
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
5089
5089
|
}
|
|
5090
|
-
const read = readNotebookStateJson(
|
|
5090
|
+
const read = readNotebookStateJson(join22(paths.journals, entry.name), paths.root, 65536);
|
|
5091
5091
|
const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
|
|
5092
5092
|
if (!journal || `${journal.operation_id}.json` !== entry.name) {
|
|
5093
5093
|
throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
|
|
@@ -5441,7 +5441,7 @@ var init_reconcile = __esm({
|
|
|
5441
5441
|
|
|
5442
5442
|
// src/notebook/entropy.ts
|
|
5443
5443
|
import { existsSync as existsSync16 } from "node:fs";
|
|
5444
|
-
import { createHash as
|
|
5444
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
5445
5445
|
import { resolve as resolve17 } from "node:path";
|
|
5446
5446
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
5447
5447
|
function listAllTrackedDocuments(config) {
|
|
@@ -5496,7 +5496,7 @@ async function compactSessionCaptures(input) {
|
|
|
5496
5496
|
const toCompact = sessionHeaders.slice(maxRecent);
|
|
5497
5497
|
const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
|
|
5498
5498
|
const archiveTitle = "Session Captures Archive (Prior Sessions)";
|
|
5499
|
-
const slugHash =
|
|
5499
|
+
const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
|
|
5500
5500
|
const archiveLogicalId = `user-note:v1:${slugHash}`;
|
|
5501
5501
|
let existingArchiveContent = "";
|
|
5502
5502
|
let existingArchiveId = null;
|
|
@@ -6064,8 +6064,8 @@ var init_capture = __esm({
|
|
|
6064
6064
|
|
|
6065
6065
|
// src/index.ts
|
|
6066
6066
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
6067
|
-
import { existsSync as existsSync26, readdirSync as
|
|
6068
|
-
import { basename as
|
|
6067
|
+
import { existsSync as existsSync26, readdirSync as readdirSync15, readFileSync as readFileSync33, statSync as statSync9 } from "node:fs";
|
|
6068
|
+
import { basename as basename13, join as join38, resolve as resolve25 } from "node:path";
|
|
6069
6069
|
import { Command as Command3, CommanderError, Option } from "commander";
|
|
6070
6070
|
|
|
6071
6071
|
// src/recipes/Recipe.ts
|
|
@@ -8042,20 +8042,20 @@ function planSkillRoots(repoRoot, options = {}) {
|
|
|
8042
8042
|
for (const name of readdirSync6(real).sort()) {
|
|
8043
8043
|
const path = join7(real, name);
|
|
8044
8044
|
const label = `${alias}/${name}`;
|
|
8045
|
-
const
|
|
8045
|
+
const entryStat2 = lstatSync6(path);
|
|
8046
8046
|
const rows = tracked.get(name) ?? [];
|
|
8047
|
-
const candidate = { alias, name, path, label, kind:
|
|
8047
|
+
const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
|
|
8048
8048
|
if (name === ".git") {
|
|
8049
8049
|
candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
|
|
8050
|
-
} else if (
|
|
8050
|
+
} else if (entryStat2.isSymbolicLink()) {
|
|
8051
8051
|
candidate.text = readlinkSync5(path);
|
|
8052
8052
|
candidate.resolution = resolveIn(diskLayout, path);
|
|
8053
8053
|
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 (!
|
|
8054
|
+
} else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
|
|
8055
8055
|
candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
|
|
8056
8056
|
} else {
|
|
8057
8057
|
candidate.staticBlock = entryHazard(path, label);
|
|
8058
|
-
if (!candidate.staticBlock && rootDevice !== void 0 &&
|
|
8058
|
+
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
8059
|
const linkRow = rows.find((row) => row.mode === "120000");
|
|
8060
8060
|
const gitlink = rows.find((row) => row.mode === "160000");
|
|
8061
8061
|
if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
|
|
@@ -10031,14 +10031,324 @@ function createG33CompanionCheck() {
|
|
|
10031
10031
|
}
|
|
10032
10032
|
|
|
10033
10033
|
// src/parity/rules.ts
|
|
10034
|
-
import { existsSync as existsSync8, lstatSync as
|
|
10035
|
-
|
|
10034
|
+
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";
|
|
10035
|
+
|
|
10036
|
+
// src/bmad/setup-scripts.ts
|
|
10037
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
10038
|
+
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";
|
|
10039
|
+
import { basename as basename4, join as join10 } from "node:path";
|
|
10040
|
+
function entryStat(path) {
|
|
10041
|
+
try {
|
|
10042
|
+
return lstatSync9(path);
|
|
10043
|
+
} catch (error) {
|
|
10044
|
+
if (error.code === "ENOENT") return void 0;
|
|
10045
|
+
throw error;
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
|
|
10049
|
+
function manifestRows(text2) {
|
|
10050
|
+
const rows = [];
|
|
10051
|
+
let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
|
|
10052
|
+
for (let i = 0; i < text2.length; i++) {
|
|
10053
|
+
const c = text2[i];
|
|
10054
|
+
if (inQuotes) {
|
|
10055
|
+
if (c === '"' && text2[i + 1] === '"') {
|
|
10056
|
+
field2 += '"';
|
|
10057
|
+
i++;
|
|
10058
|
+
} else if (c === '"') {
|
|
10059
|
+
inQuotes = false;
|
|
10060
|
+
closed = true;
|
|
10061
|
+
} else field2 += c;
|
|
10062
|
+
} else if (c === '"' && !field2 && !closed) {
|
|
10063
|
+
inQuotes = true;
|
|
10064
|
+
quoted = true;
|
|
10065
|
+
} else if (c === ",") {
|
|
10066
|
+
row.push(field2);
|
|
10067
|
+
field2 = "";
|
|
10068
|
+
quoted = closed = false;
|
|
10069
|
+
} else if (c === "\n" || c === "\r") {
|
|
10070
|
+
if (c === "\r" && text2[i + 1] === "\n") i++;
|
|
10071
|
+
row.push(field2);
|
|
10072
|
+
if (row.length !== 1 || row[0]) rows.push(row);
|
|
10073
|
+
row = [];
|
|
10074
|
+
field2 = "";
|
|
10075
|
+
quoted = closed = false;
|
|
10076
|
+
} else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
|
|
10077
|
+
else field2 += c;
|
|
10078
|
+
}
|
|
10079
|
+
if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
|
|
10080
|
+
if (row.length || field2 || closed) {
|
|
10081
|
+
row.push(field2);
|
|
10082
|
+
rows.push(row);
|
|
10083
|
+
}
|
|
10084
|
+
if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
|
|
10085
|
+
throw new Error("Invalid BMAD file manifest columns");
|
|
10086
|
+
}
|
|
10087
|
+
return rows.slice(1);
|
|
10088
|
+
}
|
|
10089
|
+
function sameIdentity(a, b) {
|
|
10090
|
+
return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
|
|
10091
|
+
}
|
|
10092
|
+
var DeclinableError = class extends Error {
|
|
10093
|
+
constructor(message2, kind) {
|
|
10094
|
+
super(message2);
|
|
10095
|
+
this.kind = kind;
|
|
10096
|
+
}
|
|
10097
|
+
};
|
|
10098
|
+
var FIELD_SEP = String.fromCharCode(0);
|
|
10099
|
+
var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
|
|
10100
|
+
function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
|
|
10101
|
+
const bmadDir = join10(repoRoot, "_bmad");
|
|
10102
|
+
const dest = join10(bmadDir, "scripts");
|
|
10103
|
+
try {
|
|
10104
|
+
const bmad = entryStat(bmadDir);
|
|
10105
|
+
if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
|
|
10106
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
|
|
10107
|
+
const destStat = entryStat(dest);
|
|
10108
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
|
|
10109
|
+
const missing = [];
|
|
10110
|
+
for (const name of RUNTIME_SCRIPTS) {
|
|
10111
|
+
const existing = destStat ? entryStat(join10(dest, name)) : void 0;
|
|
10112
|
+
if (!existing) missing.push(name);
|
|
10113
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
|
|
10114
|
+
}
|
|
10115
|
+
if (!missing.length) return { state: "present" };
|
|
10116
|
+
const configDir = entryStat(join10(bmadDir, "_config"));
|
|
10117
|
+
if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
10118
|
+
const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
|
|
10119
|
+
const manifestFile = configDir ? entryStat(manifestPath) : void 0;
|
|
10120
|
+
if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
|
|
10121
|
+
if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
|
|
10122
|
+
const installed = installedVersion?.();
|
|
10123
|
+
if (installed && installed !== version) {
|
|
10124
|
+
return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
|
|
10125
|
+
}
|
|
10126
|
+
const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
|
|
10127
|
+
const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
|
|
10128
|
+
if (unattested.length) {
|
|
10129
|
+
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 };
|
|
10130
|
+
}
|
|
10131
|
+
return { state: "missing", missing };
|
|
10132
|
+
} catch (error) {
|
|
10133
|
+
return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
|
|
10134
|
+
}
|
|
10135
|
+
}
|
|
10136
|
+
function planBmadSetupScripts(repoRoot, pkg, version) {
|
|
10137
|
+
const dest = join10(repoRoot, "_bmad", "scripts");
|
|
10138
|
+
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"}` };
|
|
10139
|
+
let fingerprint2;
|
|
10140
|
+
try {
|
|
10141
|
+
fingerprint2 = installerFingerprint(pkg.root);
|
|
10142
|
+
} catch (error) {
|
|
10143
|
+
return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
|
|
10144
|
+
}
|
|
10145
|
+
if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
|
|
10146
|
+
const source = join10(pkg.root, "src", "scripts");
|
|
10147
|
+
try {
|
|
10148
|
+
const bmad = lstatSync9(join10(repoRoot, "_bmad"));
|
|
10149
|
+
if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
|
|
10150
|
+
const destStat = entryStat(dest);
|
|
10151
|
+
if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
|
|
10152
|
+
const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
|
|
10153
|
+
if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
|
|
10154
|
+
const missing = [];
|
|
10155
|
+
for (const name of names) {
|
|
10156
|
+
const existing = entryStat(join10(dest, name));
|
|
10157
|
+
if (!existing) missing.push(name);
|
|
10158
|
+
else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
|
|
10159
|
+
}
|
|
10160
|
+
const files = [];
|
|
10161
|
+
if (!missing.length) return { files, source };
|
|
10162
|
+
const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
|
|
10163
|
+
const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
|
|
10164
|
+
const manifestFile = entryStat(manifestPath);
|
|
10165
|
+
if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
|
|
10166
|
+
throw new Error(MANIFEST_UNUSABLE);
|
|
10167
|
+
}
|
|
10168
|
+
const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
|
|
10169
|
+
for (const name of missing) {
|
|
10170
|
+
const src = join10(source, name);
|
|
10171
|
+
if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
|
|
10172
|
+
const matching = rows.filter((row) => row[3] === `scripts/${name}`);
|
|
10173
|
+
const expectedHash = createHash8("sha256").update(readFileSync10(src)).digest("hex");
|
|
10174
|
+
if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
|
|
10175
|
+
throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
|
|
10176
|
+
}
|
|
10177
|
+
files.push(join10(dest, name));
|
|
10178
|
+
}
|
|
10179
|
+
return { files, source };
|
|
10180
|
+
} catch (error) {
|
|
10181
|
+
return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
|
|
10182
|
+
}
|
|
10183
|
+
}
|
|
10184
|
+
function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
|
|
10185
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, version);
|
|
10186
|
+
if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
|
|
10187
|
+
const parent = join10(repoRoot, "_bmad");
|
|
10188
|
+
const dest = join10(parent, "scripts");
|
|
10189
|
+
const expectedParent = entryStat(parent);
|
|
10190
|
+
const expectedDir = entryStat(dest);
|
|
10191
|
+
let stage;
|
|
10192
|
+
let stageFd;
|
|
10193
|
+
let parentFd;
|
|
10194
|
+
const created = [];
|
|
10195
|
+
const stagedIdentities = /* @__PURE__ */ new Map();
|
|
10196
|
+
const published = /* @__PURE__ */ new Map();
|
|
10197
|
+
let ambiguousPublication;
|
|
10198
|
+
const matchesPayload = (path, owned) => {
|
|
10199
|
+
const stat = entryStat(path);
|
|
10200
|
+
return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync10(path).equals(owned.bytes);
|
|
10201
|
+
};
|
|
10202
|
+
let madeDir = false;
|
|
10203
|
+
let dirFd;
|
|
10204
|
+
let result2 = { files: [] };
|
|
10205
|
+
try {
|
|
10206
|
+
parentFd = openSync2(parent, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
10207
|
+
const pinnedParent = fstatSync(parentFd);
|
|
10208
|
+
if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
|
|
10209
|
+
const anchoredParent = `/proc/self/fd/${parentFd}`;
|
|
10210
|
+
stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
|
|
10211
|
+
stageFd = openSync2(stage, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
10212
|
+
const pinnedStage = fstatSync(stageFd);
|
|
10213
|
+
const stageName = basename4(stage);
|
|
10214
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
10215
|
+
if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
|
|
10216
|
+
for (const path of plan.files) {
|
|
10217
|
+
const name = basename4(path);
|
|
10218
|
+
const src = join10(plan.source, name);
|
|
10219
|
+
const staged = join10(anchoredStage, name);
|
|
10220
|
+
const fileFd = openSync2(staged, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
|
|
10221
|
+
try {
|
|
10222
|
+
const stat = fstatSync(fileFd);
|
|
10223
|
+
const bytes = readFileSync10(src);
|
|
10224
|
+
const mode = lstatSync9(src).mode & 4095;
|
|
10225
|
+
stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
|
|
10226
|
+
writeFileSync7(fileFd, bytes);
|
|
10227
|
+
fchmodSync(fileFd, mode);
|
|
10228
|
+
} finally {
|
|
10229
|
+
closeSync2(fileFd);
|
|
10230
|
+
}
|
|
10231
|
+
}
|
|
10232
|
+
if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
|
|
10233
|
+
const current = planBmadSetupScripts(repoRoot, pkg, version);
|
|
10234
|
+
if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
|
|
10235
|
+
const anchoredDest = join10(anchoredParent, "scripts");
|
|
10236
|
+
if (!entryStat(anchoredDest)) {
|
|
10237
|
+
mkdirSync6(anchoredDest);
|
|
10238
|
+
madeDir = true;
|
|
10239
|
+
}
|
|
10240
|
+
dirFd = openSync2(anchoredDest, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
|
|
10241
|
+
const pinned = fstatSync(dirFd);
|
|
10242
|
+
if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
|
|
10243
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
10244
|
+
const anchored = `/proc/self/fd/${dirFd}`;
|
|
10245
|
+
for (const path of plan.files) {
|
|
10246
|
+
const name = basename4(path);
|
|
10247
|
+
const staged = join10(anchoredStage, name);
|
|
10248
|
+
const output = join10(anchored, name);
|
|
10249
|
+
const owned = stagedIdentities.get(name);
|
|
10250
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
10251
|
+
if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
|
|
10252
|
+
if (entryStat(output)) {
|
|
10253
|
+
ambiguousPublication = path;
|
|
10254
|
+
throw new Error(`Script destination appeared before publication: ${name}`);
|
|
10255
|
+
}
|
|
10256
|
+
try {
|
|
10257
|
+
linkSync(staged, output);
|
|
10258
|
+
} catch (error) {
|
|
10259
|
+
if (entryStat(output)) ambiguousPublication = path;
|
|
10260
|
+
throw error;
|
|
10261
|
+
}
|
|
10262
|
+
published.set(path, owned);
|
|
10263
|
+
created.push(path);
|
|
10264
|
+
if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
|
|
10265
|
+
afterPublish?.(created.length);
|
|
10266
|
+
}
|
|
10267
|
+
if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
|
|
10268
|
+
for (const path of created) {
|
|
10269
|
+
if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
|
|
10270
|
+
}
|
|
10271
|
+
result2 = { files: created };
|
|
10272
|
+
} catch (error) {
|
|
10273
|
+
const recoveryErrors = [];
|
|
10274
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
10275
|
+
try {
|
|
10276
|
+
const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
10277
|
+
const stat = entryStat(anchoredPath);
|
|
10278
|
+
if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
|
|
10279
|
+
else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
10280
|
+
else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
|
|
10281
|
+
} catch (recoveryError) {
|
|
10282
|
+
recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
|
|
10283
|
+
}
|
|
10284
|
+
}
|
|
10285
|
+
if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
|
|
10286
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
10287
|
+
try {
|
|
10288
|
+
const pinned = fstatSync(dirFd);
|
|
10289
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
10290
|
+
if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
10291
|
+
else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
10292
|
+
} catch (recoveryError) {
|
|
10293
|
+
recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
|
|
10294
|
+
}
|
|
10295
|
+
}
|
|
10296
|
+
result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
|
|
10297
|
+
} finally {
|
|
10298
|
+
const cleanupErrors = [];
|
|
10299
|
+
if (stageFd !== void 0) {
|
|
10300
|
+
try {
|
|
10301
|
+
const pinnedStage = fstatSync(stageFd);
|
|
10302
|
+
const anchoredStage = `/proc/self/fd/${stageFd}`;
|
|
10303
|
+
for (const [name, owned] of stagedIdentities) {
|
|
10304
|
+
const staged = join10(anchoredStage, name);
|
|
10305
|
+
const stat = entryStat(staged);
|
|
10306
|
+
if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
|
|
10307
|
+
else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
|
|
10308
|
+
}
|
|
10309
|
+
const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
|
|
10310
|
+
if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
|
|
10311
|
+
else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
|
|
10312
|
+
} catch (cleanupError) {
|
|
10313
|
+
cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
|
|
10314
|
+
}
|
|
10315
|
+
closeSync2(stageFd);
|
|
10316
|
+
} else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
|
|
10317
|
+
if (cleanupErrors.length && result2.files.length) {
|
|
10318
|
+
for (const [path, owned] of [...published].reverse()) {
|
|
10319
|
+
try {
|
|
10320
|
+
const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
|
|
10321
|
+
if (matchesPayload(output, owned)) unlinkSync5(output);
|
|
10322
|
+
else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
|
|
10323
|
+
} catch (error) {
|
|
10324
|
+
cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
|
|
10325
|
+
}
|
|
10326
|
+
}
|
|
10327
|
+
if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
|
|
10328
|
+
try {
|
|
10329
|
+
const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
|
|
10330
|
+
if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
|
|
10331
|
+
else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
|
|
10332
|
+
} catch (error) {
|
|
10333
|
+
cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
|
|
10334
|
+
}
|
|
10335
|
+
}
|
|
10336
|
+
}
|
|
10337
|
+
if (dirFd !== void 0) closeSync2(dirFd);
|
|
10338
|
+
if (parentFd !== void 0) closeSync2(parentFd);
|
|
10339
|
+
if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
|
|
10340
|
+
}
|
|
10341
|
+
return result2;
|
|
10342
|
+
}
|
|
10343
|
+
|
|
10344
|
+
// src/parity/rules.ts
|
|
10345
|
+
import { basename as basename5, dirname as dirname9, join as join12, relative as relative9, resolve as resolve7 } from "node:path";
|
|
10036
10346
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
10037
10347
|
import YAML4 from "yaml";
|
|
10038
10348
|
|
|
10039
10349
|
// src/bmad/reserved-modules.ts
|
|
10040
|
-
import { existsSync as existsSync7, readFileSync as
|
|
10041
|
-
import { join as
|
|
10350
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10351
|
+
import { join as join11 } from "node:path";
|
|
10042
10352
|
import YAML3 from "yaml";
|
|
10043
10353
|
var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
|
|
10044
10354
|
var indentOf = (line) => line.length - line.trimStart().length;
|
|
@@ -10095,19 +10405,19 @@ function stripReservedModuleEntries(raw) {
|
|
|
10095
10405
|
return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
|
|
10096
10406
|
}
|
|
10097
10407
|
function evictReservedModuleEntries(repoRoot, dryRun) {
|
|
10098
|
-
const manifestPath =
|
|
10408
|
+
const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
10099
10409
|
const none = { evicted: [], changedFiles: [], details: [] };
|
|
10100
10410
|
if (!existsSync7(manifestPath)) return none;
|
|
10101
10411
|
let original;
|
|
10102
10412
|
try {
|
|
10103
|
-
original =
|
|
10413
|
+
original = readFileSync11(manifestPath, "utf8");
|
|
10104
10414
|
} catch {
|
|
10105
10415
|
return none;
|
|
10106
10416
|
}
|
|
10107
10417
|
const stripped = stripReservedModuleEntries(original);
|
|
10108
10418
|
if (!stripped) return none;
|
|
10109
10419
|
const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
|
|
10110
|
-
if (!dryRun)
|
|
10420
|
+
if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
|
|
10111
10421
|
return {
|
|
10112
10422
|
evicted: stripped.evicted,
|
|
10113
10423
|
changedFiles: [manifestPath],
|
|
@@ -10821,17 +11131,17 @@ function normalizeNewlines(value) {
|
|
|
10821
11131
|
return value.replace(/\r\n/g, "\n");
|
|
10822
11132
|
}
|
|
10823
11133
|
function readText(path) {
|
|
10824
|
-
return normalizeNewlines(
|
|
11134
|
+
return normalizeNewlines(readFileSync12(path, "utf8"));
|
|
10825
11135
|
}
|
|
10826
11136
|
function safeReadText2(path) {
|
|
10827
11137
|
return existsSync8(path) ? readText(path) : null;
|
|
10828
11138
|
}
|
|
10829
11139
|
function ensureParent(path) {
|
|
10830
|
-
|
|
11140
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
10831
11141
|
}
|
|
10832
11142
|
function writeText(path, content) {
|
|
10833
11143
|
ensureParent(path);
|
|
10834
|
-
|
|
11144
|
+
writeFileSync9(path, content);
|
|
10835
11145
|
}
|
|
10836
11146
|
function tryParseJson(text2) {
|
|
10837
11147
|
if (!text2) return null;
|
|
@@ -10857,12 +11167,12 @@ function readSymlinkTarget(path) {
|
|
|
10857
11167
|
}
|
|
10858
11168
|
function ensureSymlink(path, target, dryRun) {
|
|
10859
11169
|
if (existsSync8(path)) {
|
|
10860
|
-
const stat =
|
|
11170
|
+
const stat = lstatSync10(path);
|
|
10861
11171
|
if (stat.isSymbolicLink()) {
|
|
10862
11172
|
const current = readSymlinkTarget(path);
|
|
10863
11173
|
if (current === target) return { changed: false };
|
|
10864
11174
|
if (!dryRun) {
|
|
10865
|
-
|
|
11175
|
+
unlinkSync6(path);
|
|
10866
11176
|
symlinkSync3(target, path);
|
|
10867
11177
|
}
|
|
10868
11178
|
return { changed: true };
|
|
@@ -10873,12 +11183,12 @@ function ensureSymlink(path, target, dryRun) {
|
|
|
10873
11183
|
return { changed: true };
|
|
10874
11184
|
}
|
|
10875
11185
|
function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
10876
|
-
const agentsPath =
|
|
11186
|
+
const agentsPath = join12(repoRoot, "AGENTS.md");
|
|
10877
11187
|
if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
|
|
10878
11188
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
10879
|
-
const source =
|
|
11189
|
+
const source = join12(repoRoot, file);
|
|
10880
11190
|
if (!existsSync8(source)) continue;
|
|
10881
|
-
const stat =
|
|
11191
|
+
const stat = lstatSync10(source);
|
|
10882
11192
|
if (stat.isSymbolicLink()) continue;
|
|
10883
11193
|
if (stat.isFile()) {
|
|
10884
11194
|
if (!dryRun) renameSync4(source, agentsPath);
|
|
@@ -10886,9 +11196,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
10886
11196
|
}
|
|
10887
11197
|
return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
|
|
10888
11198
|
}
|
|
10889
|
-
const readmePath =
|
|
11199
|
+
const readmePath = join12(repoRoot, "README.md");
|
|
10890
11200
|
if (existsSync8(readmePath)) {
|
|
10891
|
-
const stat =
|
|
11201
|
+
const stat = lstatSync10(readmePath);
|
|
10892
11202
|
if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
|
|
10893
11203
|
if (!dryRun) copyFileSync(readmePath, agentsPath);
|
|
10894
11204
|
return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
|
|
@@ -10926,11 +11236,11 @@ function yamlGet(text2, keyPath) {
|
|
|
10926
11236
|
return "";
|
|
10927
11237
|
}
|
|
10928
11238
|
function discoverRoles(repoRoot) {
|
|
10929
|
-
const rolesDir =
|
|
11239
|
+
const rolesDir = join12(repoRoot, "agents", "hermes");
|
|
10930
11240
|
if (!existsSync8(rolesDir)) return [];
|
|
10931
|
-
return
|
|
10932
|
-
const roleDir =
|
|
10933
|
-
const roleYamlPath =
|
|
11241
|
+
return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
11242
|
+
const roleDir = join12(rolesDir, entry.name);
|
|
11243
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
10934
11244
|
if (!existsSync8(roleYamlPath)) return null;
|
|
10935
11245
|
const text2 = readText(roleYamlPath);
|
|
10936
11246
|
const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
|
|
@@ -10963,26 +11273,26 @@ function discoverRoles(repoRoot) {
|
|
|
10963
11273
|
}).filter((value) => Boolean(value));
|
|
10964
11274
|
}
|
|
10965
11275
|
function templateScript(ctx, name) {
|
|
10966
|
-
const source =
|
|
11276
|
+
const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
|
|
10967
11277
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10968
11278
|
}
|
|
10969
11279
|
function templateVersioningScript(ctx) {
|
|
10970
11280
|
return templateScript(ctx, "versioning.sh");
|
|
10971
11281
|
}
|
|
10972
11282
|
function templateLinkAgentfilesScript(ctx) {
|
|
10973
|
-
const source =
|
|
11283
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
|
|
10974
11284
|
return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
|
|
10975
11285
|
}
|
|
10976
11286
|
function templateMaterializeEnvScript(ctx) {
|
|
10977
|
-
const source =
|
|
11287
|
+
const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
|
|
10978
11288
|
return existsSync8(source) ? readText(source) : void 0;
|
|
10979
11289
|
}
|
|
10980
11290
|
function resolveAgentHooksLayer(ctx) {
|
|
10981
11291
|
const override = process.env.PJ_AGENT_HOOKS_LAYER;
|
|
10982
11292
|
if (override === "0" || override === "false") return false;
|
|
10983
11293
|
if (override === "1" || override === "true") return true;
|
|
10984
|
-
if (existsSync8(
|
|
10985
|
-
return !existsSync8(
|
|
11294
|
+
if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
|
|
11295
|
+
return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
|
|
10986
11296
|
}
|
|
10987
11297
|
function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
10988
11298
|
const out = [];
|
|
@@ -11008,13 +11318,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
|
|
|
11008
11318
|
}
|
|
11009
11319
|
function renderGeneratedProjectMiseToml(ctx, template) {
|
|
11010
11320
|
const project = readProjectJson(ctx);
|
|
11011
|
-
const projectName = String(project?.project_name ??
|
|
11321
|
+
const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
|
|
11012
11322
|
return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
|
|
11013
11323
|
}
|
|
11014
11324
|
function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
11015
|
-
const targetPath =
|
|
11325
|
+
const targetPath = join12(ctx.repoRoot, "mise.toml");
|
|
11016
11326
|
if (existsSync8(targetPath)) return null;
|
|
11017
|
-
const sourcePath =
|
|
11327
|
+
const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
|
|
11018
11328
|
if (!existsSync8(sourcePath)) return null;
|
|
11019
11329
|
const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
|
|
11020
11330
|
changedFiles.push(targetPath);
|
|
@@ -11022,12 +11332,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
|
11022
11332
|
return rendered;
|
|
11023
11333
|
}
|
|
11024
11334
|
function templateCommonProjectText(ctx, rel) {
|
|
11025
|
-
const path =
|
|
11335
|
+
const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
|
|
11026
11336
|
return existsSync8(path) ? readText(path) : void 0;
|
|
11027
11337
|
}
|
|
11028
11338
|
function lstatIfPresent(path) {
|
|
11029
11339
|
try {
|
|
11030
|
-
return
|
|
11340
|
+
return lstatSync10(path);
|
|
11031
11341
|
} catch (error) {
|
|
11032
11342
|
if (error.code === "ENOENT") return void 0;
|
|
11033
11343
|
throw error;
|
|
@@ -11035,7 +11345,7 @@ function lstatIfPresent(path) {
|
|
|
11035
11345
|
}
|
|
11036
11346
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
11037
11347
|
if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
|
|
11038
|
-
const packageJson =
|
|
11348
|
+
const packageJson = join12(repoRoot, "package.json");
|
|
11039
11349
|
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
11350
|
}
|
|
11041
11351
|
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 +11355,7 @@ function recordedProjectType(ctx) {
|
|
|
11045
11355
|
function isObsidianPluginRepo(ctx, repoRoot) {
|
|
11046
11356
|
const recorded = recordedProjectType(ctx);
|
|
11047
11357
|
if (recorded !== void 0) return recorded === "obsidian-plugin";
|
|
11048
|
-
const manifest = record2(tryParseJson(safeReadText2(
|
|
11358
|
+
const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
|
|
11049
11359
|
return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
11050
11360
|
}
|
|
11051
11361
|
function unsupportedVersionTypes(conf, script) {
|
|
@@ -11506,7 +11816,7 @@ function describeRewrite(before, after) {
|
|
|
11506
11816
|
return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
|
|
11507
11817
|
}
|
|
11508
11818
|
function readProjectJson(ctx) {
|
|
11509
|
-
return tryParseJson(safeReadText2(
|
|
11819
|
+
return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
|
|
11510
11820
|
}
|
|
11511
11821
|
function readDeclaredAgents(ctx) {
|
|
11512
11822
|
const project = readProjectJson(ctx);
|
|
@@ -11523,7 +11833,7 @@ function readDeclaredAgents(ctx) {
|
|
|
11523
11833
|
});
|
|
11524
11834
|
}
|
|
11525
11835
|
function readRoleYamlAt(roleDir) {
|
|
11526
|
-
const roleYamlPath =
|
|
11836
|
+
const roleYamlPath = join12(roleDir, "role.yaml");
|
|
11527
11837
|
if (!existsSync8(roleYamlPath)) return null;
|
|
11528
11838
|
const text2 = readText(roleYamlPath);
|
|
11529
11839
|
return {
|
|
@@ -11535,7 +11845,7 @@ function readRoleYamlAt(roleDir) {
|
|
|
11535
11845
|
}
|
|
11536
11846
|
function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
|
|
11537
11847
|
if (!roleDir) return true;
|
|
11538
|
-
return !existsSync8(
|
|
11848
|
+
return !existsSync8(join12(resolve7(repoRoot, roleDir), "role.yaml"));
|
|
11539
11849
|
}
|
|
11540
11850
|
function validateDeclaredAgent(ctx, declared) {
|
|
11541
11851
|
const details = [];
|
|
@@ -11560,11 +11870,11 @@ function validateDeclaredAgent(ctx, declared) {
|
|
|
11560
11870
|
details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
|
|
11561
11871
|
}
|
|
11562
11872
|
if (roleYaml.providerName) {
|
|
11563
|
-
const dispatcher =
|
|
11873
|
+
const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
|
|
11564
11874
|
if (!existsSync8(dispatcher)) {
|
|
11565
11875
|
details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
|
|
11566
11876
|
}
|
|
11567
|
-
const provider =
|
|
11877
|
+
const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
|
|
11568
11878
|
if (!existsSync8(provider)) {
|
|
11569
11879
|
details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
|
|
11570
11880
|
}
|
|
@@ -11580,7 +11890,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
|
|
|
11580
11890
|
function canonicalProjectJson(ctx) {
|
|
11581
11891
|
const roles = discoverRoles(ctx.repoRoot);
|
|
11582
11892
|
const existing = readProjectJson(ctx) ?? {};
|
|
11583
|
-
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(
|
|
11893
|
+
const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
|
|
11584
11894
|
const firstRole = roles[0];
|
|
11585
11895
|
const ticketProvider = {
|
|
11586
11896
|
...existing.ticket_provider,
|
|
@@ -11650,8 +11960,8 @@ function canonicalProjectJson(ctx) {
|
|
|
11650
11960
|
};
|
|
11651
11961
|
}
|
|
11652
11962
|
function projectJsonFinding(ctx) {
|
|
11653
|
-
const projectPath =
|
|
11654
|
-
const planeJsonPath =
|
|
11963
|
+
const projectPath = join12(ctx.repoRoot, ".project.json");
|
|
11964
|
+
const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
|
|
11655
11965
|
const details = [];
|
|
11656
11966
|
const data = readProjectJson(ctx);
|
|
11657
11967
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -11722,7 +12032,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
|
11722
12032
|
var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
|
|
11723
12033
|
var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
|
|
11724
12034
|
function manifestBmadModules(repoRoot) {
|
|
11725
|
-
const manifestPath =
|
|
12035
|
+
const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
11726
12036
|
const raw = safeReadText2(manifestPath);
|
|
11727
12037
|
if (raw === null) return { status: "absent" };
|
|
11728
12038
|
try {
|
|
@@ -11750,7 +12060,7 @@ function manifestBmadModules(repoRoot) {
|
|
|
11750
12060
|
}
|
|
11751
12061
|
}
|
|
11752
12062
|
function configuredBmadModuleSelection(repoRoot) {
|
|
11753
|
-
const raw = safeReadText2(
|
|
12063
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
|
|
11754
12064
|
if (raw === null) return void 0;
|
|
11755
12065
|
const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
|
|
11756
12066
|
return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
|
|
@@ -11761,24 +12071,60 @@ function selectedBmadModules(repoRoot) {
|
|
|
11761
12071
|
if (manifest.status === "invalid") throw new Error(manifest.error);
|
|
11762
12072
|
return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
|
|
11763
12073
|
}
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
12074
|
+
var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
|
|
12075
|
+
function consolidatedBmadConfig(repoRoot) {
|
|
12076
|
+
const path = join12(repoRoot, "_bmad", "config.yaml");
|
|
12077
|
+
if (!existsSync8(path)) return { status: "absent" };
|
|
12078
|
+
let parsed;
|
|
12079
|
+
try {
|
|
12080
|
+
parsed = YAML4.parse(readText(path));
|
|
12081
|
+
} catch (error) {
|
|
12082
|
+
return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
|
|
12083
|
+
}
|
|
12084
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
12085
|
+
return { status: "unusable", reason: "is not a YAML mapping" };
|
|
12086
|
+
}
|
|
12087
|
+
return { status: "mapping", sections: new Set(Object.keys(parsed)) };
|
|
12088
|
+
}
|
|
12089
|
+
function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
|
|
12090
|
+
const bmad = join12(repoRoot, "_bmad");
|
|
12091
|
+
const gaps = [];
|
|
12092
|
+
const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
|
|
12093
|
+
if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
|
|
12094
|
+
for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
|
|
12095
|
+
if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
|
|
12096
|
+
}
|
|
12097
|
+
if (absent("_config", "manifest.yaml")) {
|
|
12098
|
+
gaps.push({
|
|
12099
|
+
path: join12("_config", "manifest.yaml"),
|
|
12100
|
+
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"
|
|
12101
|
+
});
|
|
12102
|
+
}
|
|
12103
|
+
let consolidated;
|
|
12104
|
+
for (const module of modules) {
|
|
12105
|
+
if (!absent(module, "config.yaml")) continue;
|
|
12106
|
+
consolidated ??= consolidatedBmadConfig(repoRoot);
|
|
12107
|
+
if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
|
|
12108
|
+
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)`;
|
|
12109
|
+
gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
|
|
12110
|
+
}
|
|
12111
|
+
return gaps;
|
|
12112
|
+
}
|
|
12113
|
+
function bmadScaffoldIssues(repoRoot, modules) {
|
|
12114
|
+
const gaps = bmadSentinelGaps(repoRoot, modules);
|
|
12115
|
+
const nameIssues = bmadProjectNameIssues(repoRoot);
|
|
12116
|
+
return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
|
|
11771
12117
|
}
|
|
11772
12118
|
function canonicalBmadProjectName(repoRoot) {
|
|
11773
12119
|
const project = readProjectJson({ repoRoot });
|
|
11774
12120
|
const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
|
|
11775
|
-
return declared ||
|
|
12121
|
+
return declared || basename5(repoRoot);
|
|
11776
12122
|
}
|
|
11777
12123
|
function bmadProjectNameIssues(repoRoot) {
|
|
11778
12124
|
const expected = canonicalBmadProjectName(repoRoot);
|
|
11779
12125
|
const paths = [];
|
|
11780
12126
|
const details = [];
|
|
11781
|
-
const configToml =
|
|
12127
|
+
const configToml = join12(repoRoot, "_bmad", "config.toml");
|
|
11782
12128
|
const tomlText = safeReadText2(configToml);
|
|
11783
12129
|
const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
|
|
11784
12130
|
let tomlName;
|
|
@@ -11793,16 +12139,16 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
11793
12139
|
paths.push(configToml);
|
|
11794
12140
|
details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
|
|
11795
12141
|
}
|
|
11796
|
-
const bmadRoot =
|
|
12142
|
+
const bmadRoot = join12(repoRoot, "_bmad");
|
|
11797
12143
|
if (existsSync8(bmadRoot)) {
|
|
11798
12144
|
let declared;
|
|
11799
12145
|
try {
|
|
11800
12146
|
declared = selectedBmadModules(repoRoot);
|
|
11801
12147
|
} catch {
|
|
11802
|
-
declared =
|
|
12148
|
+
declared = readdirSync8(bmadRoot);
|
|
11803
12149
|
}
|
|
11804
12150
|
for (const name of new Set(declared)) {
|
|
11805
|
-
const configPath =
|
|
12151
|
+
const configPath = join12(bmadRoot, name, "config.yaml");
|
|
11806
12152
|
const raw = safeReadText2(configPath);
|
|
11807
12153
|
if (raw === null) continue;
|
|
11808
12154
|
let actual;
|
|
@@ -11822,7 +12168,7 @@ function bmadProjectNameIssues(repoRoot) {
|
|
|
11822
12168
|
function isCurrentActivation(path, activations) {
|
|
11823
12169
|
if (activations.owned.has(path)) return true;
|
|
11824
12170
|
try {
|
|
11825
|
-
if (activations.owned.has(
|
|
12171
|
+
if (activations.owned.has(join12(realpathSync4(dirname9(path)), basename5(path)))) return true;
|
|
11826
12172
|
return activations.targets.has(realpathSync4(path));
|
|
11827
12173
|
} catch {
|
|
11828
12174
|
return false;
|
|
@@ -11835,26 +12181,26 @@ async function skillActivations(ctx) {
|
|
|
11835
12181
|
function evictLegacyBmadPackState(ctx, changedFiles, activations) {
|
|
11836
12182
|
const details = [];
|
|
11837
12183
|
const skillDirs = [
|
|
11838
|
-
|
|
11839
|
-
...SUPPORTED_CLI_ROOTS.map((root) =>
|
|
12184
|
+
join12(ctx.repoRoot, ".agents", "skills"),
|
|
12185
|
+
...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
|
|
11840
12186
|
];
|
|
11841
12187
|
for (const dir of skillDirs) {
|
|
11842
12188
|
const dirStat = lstatIfPresent(dir);
|
|
11843
12189
|
if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
|
|
11844
12190
|
let names;
|
|
11845
12191
|
try {
|
|
11846
|
-
names =
|
|
12192
|
+
names = readdirSync8(dir);
|
|
11847
12193
|
} catch {
|
|
11848
12194
|
continue;
|
|
11849
12195
|
}
|
|
11850
12196
|
for (const name of names) {
|
|
11851
12197
|
if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
|
|
11852
|
-
const path =
|
|
12198
|
+
const path = join12(dir, name);
|
|
11853
12199
|
if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
|
|
11854
12200
|
if (isCurrentActivation(path, activations)) continue;
|
|
11855
12201
|
changedFiles.push(path);
|
|
11856
12202
|
details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
|
|
11857
|
-
if (!ctx.dryRun)
|
|
12203
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
11858
12204
|
}
|
|
11859
12205
|
}
|
|
11860
12206
|
return details;
|
|
@@ -11867,10 +12213,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
|
|
|
11867
12213
|
prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
|
|
11868
12214
|
};
|
|
11869
12215
|
}
|
|
11870
|
-
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
12216
|
+
function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
|
|
12217
|
+
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
11871
12218
|
const packageAt = (dir) => {
|
|
11872
12219
|
try {
|
|
11873
|
-
const parsed = JSON.parse(
|
|
12220
|
+
const parsed = JSON.parse(readFileSync12(join12(dir, "package.json"), "utf8"));
|
|
11874
12221
|
return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
|
|
11875
12222
|
} catch {
|
|
11876
12223
|
return void 0;
|
|
@@ -11902,12 +12249,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11902
12249
|
`node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
|
|
11903
12250
|
], {
|
|
11904
12251
|
encoding: "utf8",
|
|
11905
|
-
timeout:
|
|
12252
|
+
timeout: timeoutMs,
|
|
11906
12253
|
env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
|
|
11907
12254
|
});
|
|
11908
12255
|
const entry = probe.stdout?.trim().split("\n").pop()?.trim();
|
|
11909
12256
|
if (probe.status !== 0 || !entry) {
|
|
11910
|
-
const
|
|
12257
|
+
const timedOut = probe.error?.code === "ETIMEDOUT";
|
|
12258
|
+
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
12259
|
return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
|
|
11912
12260
|
}
|
|
11913
12261
|
const root = dirname9(dirname9(dirname9(entry)));
|
|
@@ -11915,6 +12263,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
|
|
|
11915
12263
|
if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
|
|
11916
12264
|
return { root, entry, version: found };
|
|
11917
12265
|
}
|
|
12266
|
+
var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
|
|
12267
|
+
var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
|
|
12268
|
+
function bmadSetupScriptsState(repoRoot) {
|
|
12269
|
+
const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
|
|
12270
|
+
const decline = (why, missing, remedy2) => ({
|
|
12271
|
+
kind: "decline",
|
|
12272
|
+
summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
|
|
12273
|
+
});
|
|
12274
|
+
switch (inspection.state) {
|
|
12275
|
+
case "present":
|
|
12276
|
+
return { kind: "present" };
|
|
12277
|
+
case "unsafe":
|
|
12278
|
+
return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
|
|
12279
|
+
case "decline":
|
|
12280
|
+
return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
|
|
12281
|
+
case "missing":
|
|
12282
|
+
break;
|
|
12283
|
+
}
|
|
12284
|
+
const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
|
|
12285
|
+
const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
|
|
12286
|
+
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
|
|
12287
|
+
if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
|
|
12288
|
+
const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
12289
|
+
if (plan.error) {
|
|
12290
|
+
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 };
|
|
12291
|
+
}
|
|
12292
|
+
return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
|
|
12293
|
+
}
|
|
11918
12294
|
function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
|
|
11919
12295
|
const installerModules = modules.length ? modules.join(",") : "core";
|
|
11920
12296
|
return [
|
|
@@ -11933,7 +12309,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
|
|
|
11933
12309
|
}
|
|
11934
12310
|
function bmadUpgradeChannelArgs(repoRoot, version) {
|
|
11935
12311
|
if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
|
|
11936
|
-
const raw = safeReadText2(
|
|
12312
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
11937
12313
|
if (!raw) return [];
|
|
11938
12314
|
let modules;
|
|
11939
12315
|
try {
|
|
@@ -12003,7 +12379,7 @@ function bmadChannelTarget(distTags) {
|
|
|
12003
12379
|
return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
|
|
12004
12380
|
}
|
|
12005
12381
|
function readInstalledBmadVersion(repoRoot) {
|
|
12006
|
-
const raw = safeReadText2(
|
|
12382
|
+
const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
12007
12383
|
if (!raw) return void 0;
|
|
12008
12384
|
try {
|
|
12009
12385
|
const parsed = YAML4.parse(raw);
|
|
@@ -12014,8 +12390,8 @@ function readInstalledBmadVersion(repoRoot) {
|
|
|
12014
12390
|
}
|
|
12015
12391
|
}
|
|
12016
12392
|
function bmadCachePath(homeDir) {
|
|
12017
|
-
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() ||
|
|
12018
|
-
return
|
|
12393
|
+
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
|
|
12394
|
+
return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
|
|
12019
12395
|
}
|
|
12020
12396
|
function readBmadDistTagsCache(homeDir) {
|
|
12021
12397
|
const raw = safeReadText2(bmadCachePath(homeDir));
|
|
@@ -12057,8 +12433,8 @@ function resolveBmadDistTags(homeDir) {
|
|
|
12057
12433
|
if (fetched) {
|
|
12058
12434
|
try {
|
|
12059
12435
|
const path = bmadCachePath(homeDir);
|
|
12060
|
-
|
|
12061
|
-
|
|
12436
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
12437
|
+
writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
|
|
12062
12438
|
} catch {
|
|
12063
12439
|
}
|
|
12064
12440
|
return { distTags: fetched, stale: false };
|
|
@@ -12101,27 +12477,27 @@ function compareBmadVersions(a, b) {
|
|
|
12101
12477
|
function discoverMomoProviderCandidates(repoRoot) {
|
|
12102
12478
|
const candidates = [];
|
|
12103
12479
|
const roleDirs = [];
|
|
12104
|
-
const hermesDir =
|
|
12480
|
+
const hermesDir = join12(repoRoot, "agents", "hermes");
|
|
12105
12481
|
if (existsSync8(hermesDir)) {
|
|
12106
|
-
for (const entry of
|
|
12107
|
-
if (entry.isDirectory()) roleDirs.push(
|
|
12482
|
+
for (const entry of readdirSync8(hermesDir, { withFileTypes: true })) {
|
|
12483
|
+
if (entry.isDirectory()) roleDirs.push(join12(hermesDir, entry.name));
|
|
12108
12484
|
}
|
|
12109
12485
|
}
|
|
12110
12486
|
for (const roleDir of roleDirs) {
|
|
12111
12487
|
for (const name of ["momo", "provider", "momo-provider"]) {
|
|
12112
|
-
const path =
|
|
12488
|
+
const path = join12(roleDir, name);
|
|
12113
12489
|
if (existsSync8(path)) {
|
|
12114
12490
|
const kind = path.endsWith(".py") ? "python" : "shell";
|
|
12115
12491
|
candidates.push({ path, kind });
|
|
12116
12492
|
}
|
|
12117
12493
|
}
|
|
12118
12494
|
if (existsSync8(roleDir)) {
|
|
12119
|
-
for (const entry of
|
|
12495
|
+
for (const entry of readdirSync8(roleDir, { withFileTypes: true })) {
|
|
12120
12496
|
if (!entry.isFile()) continue;
|
|
12121
12497
|
if (!entry.name.startsWith("momo")) continue;
|
|
12122
|
-
const path =
|
|
12498
|
+
const path = join12(roleDir, entry.name);
|
|
12123
12499
|
try {
|
|
12124
|
-
if (
|
|
12500
|
+
if (lstatSync10(path).mode & 73) {
|
|
12125
12501
|
const kind = entry.name.endsWith(".py") ? "python" : "shell";
|
|
12126
12502
|
if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
|
|
12127
12503
|
}
|
|
@@ -12131,7 +12507,7 @@ function discoverMomoProviderCandidates(repoRoot) {
|
|
|
12131
12507
|
}
|
|
12132
12508
|
}
|
|
12133
12509
|
for (const rel of [".mise/scripts/momo-provider.sh", ".scripts/momo-provider.sh", "momo"]) {
|
|
12134
|
-
const path =
|
|
12510
|
+
const path = join12(repoRoot, rel);
|
|
12135
12511
|
if (existsSync8(path)) {
|
|
12136
12512
|
const kind = path.endsWith(".py") ? "python" : "shell";
|
|
12137
12513
|
if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
|
|
@@ -12167,7 +12543,7 @@ function runProviderLocalSmoke(repoRoot, candidate) {
|
|
|
12167
12543
|
return { ok: true };
|
|
12168
12544
|
}
|
|
12169
12545
|
function attemptPlaneStateMapping(repoRoot) {
|
|
12170
|
-
const project = tryParseJson(safeReadText2(
|
|
12546
|
+
const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
|
|
12171
12547
|
const tp = project?.ticket_provider ?? {};
|
|
12172
12548
|
if (!tp.board_id) return { ok: false, detail: "ticket_provider.board_id missing; cannot map Plane states" };
|
|
12173
12549
|
return { ok: false, detail: `Plane state mapping attempted for board ${tp.board_id} (credentials required for full mapping)` };
|
|
@@ -12184,7 +12560,7 @@ function momoLifecycleFinding(section2, status, summary, details = []) {
|
|
|
12184
12560
|
}
|
|
12185
12561
|
function auditManifestRoleConsistency(repoRoot) {
|
|
12186
12562
|
const details = [];
|
|
12187
|
-
const projectPath =
|
|
12563
|
+
const projectPath = join12(repoRoot, ".project.json");
|
|
12188
12564
|
if (!existsSync8(projectPath)) {
|
|
12189
12565
|
return momoLifecycleFinding("manifest-role-consistency", "fail", ".project.json missing", [".project.json missing"]);
|
|
12190
12566
|
}
|
|
@@ -12206,7 +12582,7 @@ function auditManifestRoleConsistency(repoRoot) {
|
|
|
12206
12582
|
details.push(`agents.${agentId}.role_dir does not exist: ${agent.role_dir}`);
|
|
12207
12583
|
continue;
|
|
12208
12584
|
}
|
|
12209
|
-
const roleYaml =
|
|
12585
|
+
const roleYaml = join12(roleDir, "role.yaml");
|
|
12210
12586
|
if (!existsSync8(roleYaml)) {
|
|
12211
12587
|
details.push(`agents.${agentId} role.yaml missing at ${agent.role_dir}/role.yaml`);
|
|
12212
12588
|
continue;
|
|
@@ -12249,26 +12625,26 @@ function hasAnyLifecycleScript(repoRoot) {
|
|
|
12249
12625
|
for (const pattern of patterns) {
|
|
12250
12626
|
if (pattern.includes("*")) {
|
|
12251
12627
|
const [prefix, suffix] = pattern.split("*");
|
|
12252
|
-
const base =
|
|
12628
|
+
const base = join12(repoRoot, prefix);
|
|
12253
12629
|
if (!existsSync8(base)) continue;
|
|
12254
|
-
for (const entry of
|
|
12630
|
+
for (const entry of readdirSync8(base, { withFileTypes: true })) {
|
|
12255
12631
|
if (!entry.isDirectory()) continue;
|
|
12256
|
-
const candidate =
|
|
12632
|
+
const candidate = join12(base, entry.name, suffix);
|
|
12257
12633
|
if (existsSync8(candidate)) return true;
|
|
12258
12634
|
const parent = dirname9(candidate);
|
|
12259
|
-
const stem =
|
|
12635
|
+
const stem = basename5(candidate);
|
|
12260
12636
|
if (!existsSync8(parent)) continue;
|
|
12261
|
-
for (const sibling of
|
|
12637
|
+
for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
|
|
12262
12638
|
if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
|
|
12263
12639
|
}
|
|
12264
12640
|
}
|
|
12265
12641
|
} else {
|
|
12266
|
-
const base =
|
|
12642
|
+
const base = join12(repoRoot, pattern);
|
|
12267
12643
|
if (existsSync8(base)) return true;
|
|
12268
12644
|
const parent = dirname9(base);
|
|
12269
|
-
const prefix =
|
|
12645
|
+
const prefix = basename5(base);
|
|
12270
12646
|
if (existsSync8(parent)) {
|
|
12271
|
-
for (const entry of
|
|
12647
|
+
for (const entry of readdirSync8(parent, { withFileTypes: true })) {
|
|
12272
12648
|
if (entry.name.startsWith(prefix)) return true;
|
|
12273
12649
|
}
|
|
12274
12650
|
}
|
|
@@ -12298,26 +12674,26 @@ function hasAnySentinelScript(repoRoot) {
|
|
|
12298
12674
|
for (const pattern of patterns) {
|
|
12299
12675
|
if (pattern.includes("*")) {
|
|
12300
12676
|
const [prefix, suffix] = pattern.split("*");
|
|
12301
|
-
const base =
|
|
12677
|
+
const base = join12(repoRoot, prefix);
|
|
12302
12678
|
if (!existsSync8(base)) continue;
|
|
12303
|
-
for (const entry of
|
|
12679
|
+
for (const entry of readdirSync8(base, { withFileTypes: true })) {
|
|
12304
12680
|
if (!entry.isDirectory()) continue;
|
|
12305
|
-
const candidate =
|
|
12681
|
+
const candidate = join12(base, entry.name, suffix);
|
|
12306
12682
|
if (existsSync8(candidate)) return true;
|
|
12307
12683
|
const parent = dirname9(candidate);
|
|
12308
|
-
const stem =
|
|
12684
|
+
const stem = basename5(candidate);
|
|
12309
12685
|
if (!existsSync8(parent)) continue;
|
|
12310
|
-
for (const sibling of
|
|
12686
|
+
for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
|
|
12311
12687
|
if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
|
|
12312
12688
|
}
|
|
12313
12689
|
}
|
|
12314
12690
|
} else {
|
|
12315
|
-
const base =
|
|
12691
|
+
const base = join12(repoRoot, pattern);
|
|
12316
12692
|
if (existsSync8(base)) return true;
|
|
12317
12693
|
const parent = dirname9(base);
|
|
12318
|
-
const prefix =
|
|
12694
|
+
const prefix = basename5(base);
|
|
12319
12695
|
if (existsSync8(parent)) {
|
|
12320
|
-
for (const entry of
|
|
12696
|
+
for (const entry of readdirSync8(parent, { withFileTypes: true })) {
|
|
12321
12697
|
if (entry.name.startsWith(prefix)) return true;
|
|
12322
12698
|
}
|
|
12323
12699
|
}
|
|
@@ -12362,7 +12738,7 @@ function auditProviderSyntax(repoRoot) {
|
|
|
12362
12738
|
}
|
|
12363
12739
|
function auditPlaneBinding(repoRoot) {
|
|
12364
12740
|
const details = [];
|
|
12365
|
-
const project = tryParseJson(safeReadText2(
|
|
12741
|
+
const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
|
|
12366
12742
|
const tp = project?.ticket_provider ?? {};
|
|
12367
12743
|
for (const key of ["type", "workspace", "identifier", "board_id"]) {
|
|
12368
12744
|
if (!tp[key]) details.push(`ticket_provider.${key} missing`);
|
|
@@ -12534,8 +12910,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
|
|
|
12534
12910
|
".zencoder": "zencoder"
|
|
12535
12911
|
};
|
|
12536
12912
|
function unsupportedRootAttestation(repoRoot, rootName) {
|
|
12537
|
-
const root =
|
|
12538
|
-
const stat =
|
|
12913
|
+
const root = join12(repoRoot, rootName);
|
|
12914
|
+
const stat = lstatSync10(root);
|
|
12539
12915
|
if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
|
|
12540
12916
|
const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
|
|
12541
12917
|
if (!installedBmadTools(repoRoot).has(installerTool)) {
|
|
@@ -12554,7 +12930,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
|
|
|
12554
12930
|
function optionalTemplateScriptIssues(ctx) {
|
|
12555
12931
|
const issues = [];
|
|
12556
12932
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
12557
|
-
const path =
|
|
12933
|
+
const path = join12(ctx.repoRoot, rel);
|
|
12558
12934
|
if (!existsSync8(path)) continue;
|
|
12559
12935
|
const expected = templateCommonProjectText(ctx, rel);
|
|
12560
12936
|
if (expected === void 0) continue;
|
|
@@ -12565,7 +12941,7 @@ function optionalTemplateScriptIssues(ctx) {
|
|
|
12565
12941
|
function refreshOptionalTemplateScripts(ctx) {
|
|
12566
12942
|
const changed = [];
|
|
12567
12943
|
for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
|
|
12568
|
-
const path =
|
|
12944
|
+
const path = join12(ctx.repoRoot, rel);
|
|
12569
12945
|
if (!existsSync8(path)) continue;
|
|
12570
12946
|
const expected = templateCommonProjectText(ctx, rel);
|
|
12571
12947
|
if (expected === void 0 || safeReadText2(path) === expected) continue;
|
|
@@ -12583,13 +12959,13 @@ function createMiseChecks() {
|
|
|
12583
12959
|
id: "mise.config-root",
|
|
12584
12960
|
title: "mise config_root + AGENTS link hooks",
|
|
12585
12961
|
audit: (ctx) => {
|
|
12586
|
-
const misePath =
|
|
12962
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
12587
12963
|
if (!existsSync8(misePath)) {
|
|
12588
12964
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
12589
12965
|
}
|
|
12590
12966
|
const text2 = readText(misePath);
|
|
12591
12967
|
const details = [];
|
|
12592
|
-
const linkAgentfilesPath =
|
|
12968
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
12593
12969
|
if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
12594
12970
|
const toml = [];
|
|
12595
12971
|
const manual = [];
|
|
@@ -12637,7 +13013,7 @@ function createMiseChecks() {
|
|
|
12637
13013
|
};
|
|
12638
13014
|
},
|
|
12639
13015
|
migrate: (ctx, finding2) => {
|
|
12640
|
-
const path =
|
|
13016
|
+
const path = join12(ctx.repoRoot, "mise.toml");
|
|
12641
13017
|
const changedFiles = [];
|
|
12642
13018
|
const details = [];
|
|
12643
13019
|
if (!existsSync8(path)) {
|
|
@@ -12656,7 +13032,7 @@ function createMiseChecks() {
|
|
|
12656
13032
|
if (!ctx.dryRun) writeText(path, plan.text);
|
|
12657
13033
|
text2 = plan.text;
|
|
12658
13034
|
}
|
|
12659
|
-
const linkAgentfilesPath =
|
|
13035
|
+
const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
12660
13036
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
12661
13037
|
if (expectedScript === void 0) {
|
|
12662
13038
|
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 +13070,9 @@ function createMiseChecks() {
|
|
|
12694
13070
|
title: "managed mise versioning block",
|
|
12695
13071
|
audit: (ctx) => {
|
|
12696
13072
|
const details = [];
|
|
12697
|
-
const misePath =
|
|
12698
|
-
const versioningPath =
|
|
12699
|
-
const manifestPath =
|
|
13073
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
13074
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
13075
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
12700
13076
|
const text2 = safeReadText2(misePath);
|
|
12701
13077
|
if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
12702
13078
|
if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
@@ -12722,7 +13098,7 @@ function createMiseChecks() {
|
|
|
12722
13098
|
migrate: (ctx, finding2) => {
|
|
12723
13099
|
const changedFiles = [];
|
|
12724
13100
|
const details = [];
|
|
12725
|
-
const misePath =
|
|
13101
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
12726
13102
|
if (!existsSync8(misePath)) {
|
|
12727
13103
|
if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
|
|
12728
13104
|
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 +13116,7 @@ function createMiseChecks() {
|
|
|
12740
13116
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
12741
13117
|
if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
|
|
12742
13118
|
}
|
|
12743
|
-
const versioningPath =
|
|
13119
|
+
const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
12744
13120
|
const expectedScript = templateVersioningScript(ctx);
|
|
12745
13121
|
if (expectedScript === void 0) {
|
|
12746
13122
|
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 +13128,7 @@ function createMiseChecks() {
|
|
|
12752
13128
|
chmodSync3(versioningPath, 493);
|
|
12753
13129
|
}
|
|
12754
13130
|
}
|
|
12755
|
-
const manifestPath =
|
|
13131
|
+
const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
|
|
12756
13132
|
const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
|
|
12757
13133
|
if (safeReadText2(manifestPath) !== expectedManifest) {
|
|
12758
13134
|
changedFiles.push(manifestPath);
|
|
@@ -12814,7 +13190,7 @@ var RETIRED_SKILL_SCRIPTS = [
|
|
|
12814
13190
|
".mise/scripts/unlink-project-skills-from-clis.sh"
|
|
12815
13191
|
];
|
|
12816
13192
|
function retiredSkillsScripts(ctx) {
|
|
12817
|
-
return RETIRED_SKILL_SCRIPTS.map((path) =>
|
|
13193
|
+
return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
|
|
12818
13194
|
}
|
|
12819
13195
|
function skillsWiringFindings(text2) {
|
|
12820
13196
|
if (text2 === null) return { fixable: [], manual: [] };
|
|
@@ -12865,7 +13241,7 @@ function createAgentHooksChecks() {
|
|
|
12865
13241
|
title: "Skillex project skills",
|
|
12866
13242
|
audit: async (ctx) => {
|
|
12867
13243
|
const finding2 = await auditProjectSkills(ctx);
|
|
12868
|
-
const mise = safeReadText2(
|
|
13244
|
+
const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
12869
13245
|
const registryField = await legacyRegistryField(ctx);
|
|
12870
13246
|
const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
|
|
12871
13247
|
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 +13262,7 @@ function createAgentHooksChecks() {
|
|
|
12886
13262
|
const changedFiles = [];
|
|
12887
13263
|
const details = [];
|
|
12888
13264
|
const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
|
|
12889
|
-
let currentMise = safeReadText2(
|
|
13265
|
+
let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
|
|
12890
13266
|
if (currentMise === null) {
|
|
12891
13267
|
const template = templateCommonProjectText(ctx, "mise.toml.jinja");
|
|
12892
13268
|
if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
|
|
@@ -12901,9 +13277,9 @@ function createAgentHooksChecks() {
|
|
|
12901
13277
|
const stat = lstatIfPresent(path);
|
|
12902
13278
|
if (!stat || stat.isDirectory()) continue;
|
|
12903
13279
|
changedFiles.push(path);
|
|
12904
|
-
if (!ctx.dryRun)
|
|
13280
|
+
if (!ctx.dryRun) unlinkSync6(path);
|
|
12905
13281
|
}
|
|
12906
|
-
const misePath =
|
|
13282
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
12907
13283
|
const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
|
|
12908
13284
|
if (safeReadText2(misePath) !== nextMise) {
|
|
12909
13285
|
changedFiles.push(misePath);
|
|
@@ -12934,9 +13310,9 @@ function createAgentHooksChecks() {
|
|
|
12934
13310
|
id: "sot.agent-symlinks",
|
|
12935
13311
|
title: "AGENTS/CLAUDE/GEMINI symlink contract",
|
|
12936
13312
|
audit: (ctx) => {
|
|
12937
|
-
const agentsPath =
|
|
13313
|
+
const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
|
|
12938
13314
|
if (!existsSync8(agentsPath)) {
|
|
12939
|
-
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(
|
|
13315
|
+
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
|
|
12940
13316
|
if (fallbackSources.length === 0) {
|
|
12941
13317
|
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
13318
|
}
|
|
@@ -12951,7 +13327,7 @@ function createAgentHooksChecks() {
|
|
|
12951
13327
|
}
|
|
12952
13328
|
const details = [];
|
|
12953
13329
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
12954
|
-
const full =
|
|
13330
|
+
const full = join12(ctx.repoRoot, file);
|
|
12955
13331
|
const target = readSymlinkTarget(full);
|
|
12956
13332
|
if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
|
|
12957
13333
|
}
|
|
@@ -12975,7 +13351,7 @@ function createAgentHooksChecks() {
|
|
|
12975
13351
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
|
|
12976
13352
|
}
|
|
12977
13353
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
12978
|
-
const full =
|
|
13354
|
+
const full = join12(ctx.repoRoot, file);
|
|
12979
13355
|
const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
|
|
12980
13356
|
if (result2.blocked) blockedDetails.push(result2.blocked);
|
|
12981
13357
|
if (result2.changed) changedFiles.push(full);
|
|
@@ -13002,7 +13378,7 @@ function createProjectJsonChecks() {
|
|
|
13002
13378
|
const changedFiles = [];
|
|
13003
13379
|
const blockedDetails = [];
|
|
13004
13380
|
const droppedDetails = [];
|
|
13005
|
-
const path =
|
|
13381
|
+
const path = join12(ctx.repoRoot, ".project.json");
|
|
13006
13382
|
const existing = readProjectJson(ctx) ?? {};
|
|
13007
13383
|
const canonical = canonicalProjectJson(ctx);
|
|
13008
13384
|
const preservedDetails = [];
|
|
@@ -13034,7 +13410,7 @@ function createProjectJsonChecks() {
|
|
|
13034
13410
|
changedFiles.push(path);
|
|
13035
13411
|
if (!ctx.dryRun) writeText(path, expected);
|
|
13036
13412
|
}
|
|
13037
|
-
const planeJson =
|
|
13413
|
+
const planeJson = join12(ctx.repoRoot, ".plane.json");
|
|
13038
13414
|
if (existsSync8(planeJson)) {
|
|
13039
13415
|
const backup = `${planeJson}.migrated-backup`;
|
|
13040
13416
|
if (existsSync8(backup)) {
|
|
@@ -13066,10 +13442,10 @@ function createMiseOpInjectChecks() {
|
|
|
13066
13442
|
const details = [];
|
|
13067
13443
|
let envOpNeedsHands = false;
|
|
13068
13444
|
const optedOut = envMaterializationOptedOut(ctx);
|
|
13069
|
-
const envOpPath =
|
|
13445
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
13070
13446
|
const envOpExists = existsSync8(envOpPath);
|
|
13071
13447
|
const envOp = envOpExists ? readText(envOpPath) : void 0;
|
|
13072
|
-
const gitignore = safeReadText2(
|
|
13448
|
+
const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
|
|
13073
13449
|
if (!envOpExists) {
|
|
13074
13450
|
details.push(".env.op missing");
|
|
13075
13451
|
} else if (!envOp?.trim()) {
|
|
@@ -13089,7 +13465,7 @@ function createMiseOpInjectChecks() {
|
|
|
13089
13465
|
if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
|
|
13090
13466
|
if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
|
|
13091
13467
|
if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
|
|
13092
|
-
const misePath =
|
|
13468
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
13093
13469
|
const miseText = safeReadText2(misePath);
|
|
13094
13470
|
if (!miseText) {
|
|
13095
13471
|
details.push("mise.toml missing for .env materialization hook");
|
|
@@ -13112,7 +13488,7 @@ function createMiseOpInjectChecks() {
|
|
|
13112
13488
|
else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
|
|
13113
13489
|
details.push(...mise);
|
|
13114
13490
|
}
|
|
13115
|
-
const materializePath =
|
|
13491
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
13116
13492
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
13117
13493
|
if (optedOut) {
|
|
13118
13494
|
if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
|
|
@@ -13120,7 +13496,7 @@ function createMiseOpInjectChecks() {
|
|
|
13120
13496
|
details.push("pjangler package is missing the managed materialize-env.sh source");
|
|
13121
13497
|
} else if (safeReadText2(materializePath) !== expectedMaterializer) {
|
|
13122
13498
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
|
|
13123
|
-
} else if ((
|
|
13499
|
+
} else if ((lstatSync10(materializePath).mode & 73) === 0) {
|
|
13124
13500
|
details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
|
|
13125
13501
|
}
|
|
13126
13502
|
return {
|
|
@@ -13136,8 +13512,8 @@ function createMiseOpInjectChecks() {
|
|
|
13136
13512
|
const changedFiles = [];
|
|
13137
13513
|
const details = [];
|
|
13138
13514
|
let envOpNeedsHands = false;
|
|
13139
|
-
const envOpPath =
|
|
13140
|
-
const canonicalEnvOpPath =
|
|
13515
|
+
const envOpPath = join12(ctx.repoRoot, ".env.op");
|
|
13516
|
+
const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
|
|
13141
13517
|
if (!existsSync8(canonicalEnvOpPath)) {
|
|
13142
13518
|
return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
|
|
13143
13519
|
}
|
|
@@ -13166,7 +13542,7 @@ function createMiseOpInjectChecks() {
|
|
|
13166
13542
|
}
|
|
13167
13543
|
}
|
|
13168
13544
|
}
|
|
13169
|
-
const gitignorePath =
|
|
13545
|
+
const gitignorePath = join12(ctx.repoRoot, ".gitignore");
|
|
13170
13546
|
const gitignore = safeReadText2(gitignorePath) ?? "";
|
|
13171
13547
|
const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
|
|
13172
13548
|
# staged through a mktemp file and moved into place only on success.
|
|
@@ -13179,7 +13555,7 @@ function createMiseOpInjectChecks() {
|
|
|
13179
13555
|
changedFiles.push(gitignorePath);
|
|
13180
13556
|
if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
|
|
13181
13557
|
}
|
|
13182
|
-
const misePath =
|
|
13558
|
+
const misePath = join12(ctx.repoRoot, "mise.toml");
|
|
13183
13559
|
let currentMise = safeReadText2(misePath);
|
|
13184
13560
|
if (currentMise === null) {
|
|
13185
13561
|
const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
|
|
@@ -13196,7 +13572,7 @@ function createMiseOpInjectChecks() {
|
|
|
13196
13572
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
13197
13573
|
if (!ctx.dryRun) writeText(misePath, opPlan.text);
|
|
13198
13574
|
}
|
|
13199
|
-
const materializePath =
|
|
13575
|
+
const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
|
|
13200
13576
|
const expectedMaterializer = templateMaterializeEnvScript(ctx);
|
|
13201
13577
|
if (optedOut) {
|
|
13202
13578
|
if (existsSync8(materializePath)) {
|
|
@@ -13205,7 +13581,7 @@ function createMiseOpInjectChecks() {
|
|
|
13205
13581
|
}
|
|
13206
13582
|
} else if (!expectedMaterializer) {
|
|
13207
13583
|
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) && (
|
|
13584
|
+
} else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
|
|
13209
13585
|
changedFiles.push(materializePath);
|
|
13210
13586
|
if (!ctx.dryRun) {
|
|
13211
13587
|
writeText(materializePath, expectedMaterializer);
|
|
@@ -13267,7 +13643,7 @@ function createProjectProvenanceChecks() {
|
|
|
13267
13643
|
title: ".copier-answers.yml provenance + drift report",
|
|
13268
13644
|
audit: (ctx) => {
|
|
13269
13645
|
const details = [];
|
|
13270
|
-
const path =
|
|
13646
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
13271
13647
|
const text2 = safeReadText2(path);
|
|
13272
13648
|
const project = readProjectJson(ctx);
|
|
13273
13649
|
if (!text2) {
|
|
@@ -13295,11 +13671,11 @@ function createProjectProvenanceChecks() {
|
|
|
13295
13671
|
migrate: (ctx, finding2) => {
|
|
13296
13672
|
const changedFiles = [];
|
|
13297
13673
|
const project = canonicalProjectJson(ctx);
|
|
13298
|
-
const path =
|
|
13674
|
+
const path = join12(ctx.repoRoot, ".copier-answers.yml");
|
|
13299
13675
|
const current = safeReadText2(path);
|
|
13300
13676
|
const existing = current ? YAML4.parseDocument(current) : void 0;
|
|
13301
13677
|
const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
|
|
13302
|
-
document.set("_src_path",
|
|
13678
|
+
document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
|
|
13303
13679
|
document.set("project_description", String(project.project_description));
|
|
13304
13680
|
document.set("project_name", String(project.project_name));
|
|
13305
13681
|
document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
|
|
@@ -13324,9 +13700,9 @@ ${body}`;
|
|
|
13324
13700
|
}
|
|
13325
13701
|
function supportedCliProjectionIssues(repoRoot, plan) {
|
|
13326
13702
|
const issues = [];
|
|
13327
|
-
const managedSkills =
|
|
13703
|
+
const managedSkills = join12(repoRoot, ".agents", "skills");
|
|
13328
13704
|
for (const rootName of SUPPORTED_CLI_ROOTS) {
|
|
13329
|
-
const root =
|
|
13705
|
+
const root = join12(repoRoot, rootName);
|
|
13330
13706
|
const rootStat = lstatIfPresent(root);
|
|
13331
13707
|
if (!rootStat) {
|
|
13332
13708
|
issues.push(`${rootName} missing`);
|
|
@@ -13336,7 +13712,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
13336
13712
|
issues.push(`${rootName} must be a real configuration directory`);
|
|
13337
13713
|
continue;
|
|
13338
13714
|
}
|
|
13339
|
-
const skills =
|
|
13715
|
+
const skills = join12(root, "skills");
|
|
13340
13716
|
const skillsStat = lstatIfPresent(skills);
|
|
13341
13717
|
if (!skillsStat) {
|
|
13342
13718
|
issues.push(`${rootName}/skills missing`);
|
|
@@ -13373,9 +13749,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
|
|
|
13373
13749
|
issues.push(`${rootName}/skills is not a directory`);
|
|
13374
13750
|
continue;
|
|
13375
13751
|
}
|
|
13376
|
-
const hasGeneratedSkill = existsSync8(projectedSkills) &&
|
|
13377
|
-
const skillFile =
|
|
13378
|
-
return existsSync8(skillFile) &&
|
|
13752
|
+
const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
|
|
13753
|
+
const skillFile = join12(projectedSkills, name, "SKILL.md");
|
|
13754
|
+
return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
|
|
13379
13755
|
});
|
|
13380
13756
|
if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
|
|
13381
13757
|
}
|
|
@@ -13401,7 +13777,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
|
|
|
13401
13777
|
"# some CLIs get a real directory here and some get a symlink."
|
|
13402
13778
|
]);
|
|
13403
13779
|
function supportedCliGitignoreIssues(repoRoot) {
|
|
13404
|
-
const lines = (safeReadText2(
|
|
13780
|
+
const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
|
|
13405
13781
|
const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
|
|
13406
13782
|
return [
|
|
13407
13783
|
...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
|
|
@@ -13409,7 +13785,7 @@ function supportedCliGitignoreIssues(repoRoot) {
|
|
|
13409
13785
|
];
|
|
13410
13786
|
}
|
|
13411
13787
|
function ensureSupportedCliGitignore(ctx) {
|
|
13412
|
-
const path =
|
|
13788
|
+
const path = join12(ctx.repoRoot, ".gitignore");
|
|
13413
13789
|
const current = safeReadText2(path) ?? "";
|
|
13414
13790
|
const kept = current.split(/\r?\n/).filter((line) => {
|
|
13415
13791
|
const trimmed2 = line.trim();
|
|
@@ -13451,6 +13827,24 @@ function withInstallerSkillHandoff(check) {
|
|
|
13451
13827
|
}
|
|
13452
13828
|
};
|
|
13453
13829
|
}
|
|
13830
|
+
function withScaffoldPostcondition(check) {
|
|
13831
|
+
if (check.id !== "bmad.scaffold") return check;
|
|
13832
|
+
return {
|
|
13833
|
+
...check,
|
|
13834
|
+
migrate: async (ctx, finding2) => {
|
|
13835
|
+
const result2 = await check.migrate(ctx, finding2);
|
|
13836
|
+
if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
|
|
13837
|
+
const after = await check.audit(ctx);
|
|
13838
|
+
if (after.status === "pass" || after.status === "skip") return result2;
|
|
13839
|
+
return {
|
|
13840
|
+
...result2,
|
|
13841
|
+
status: "partial",
|
|
13842
|
+
summary: after.summary,
|
|
13843
|
+
details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
|
|
13844
|
+
};
|
|
13845
|
+
}
|
|
13846
|
+
};
|
|
13847
|
+
}
|
|
13454
13848
|
function createBmadChecks() {
|
|
13455
13849
|
return [
|
|
13456
13850
|
{
|
|
@@ -13468,15 +13862,8 @@ function createBmadChecks() {
|
|
|
13468
13862
|
fixable: false
|
|
13469
13863
|
};
|
|
13470
13864
|
}
|
|
13471
|
-
const targetRoot = join11(ctx.repoRoot, "_bmad");
|
|
13472
13865
|
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
|
-
];
|
|
13866
|
+
const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13480
13867
|
return {
|
|
13481
13868
|
id: "bmad.scaffold",
|
|
13482
13869
|
title: "BMAD modules/docs scaffold",
|
|
@@ -13515,7 +13902,7 @@ function createBmadChecks() {
|
|
|
13515
13902
|
bmadCompanion: preservation
|
|
13516
13903
|
};
|
|
13517
13904
|
if (ctx.dryRun) {
|
|
13518
|
-
const
|
|
13905
|
+
const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13519
13906
|
const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
|
|
13520
13907
|
if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
|
|
13521
13908
|
return {
|
|
@@ -13528,8 +13915,8 @@ function createBmadChecks() {
|
|
|
13528
13915
|
bmadCompanion: companion
|
|
13529
13916
|
};
|
|
13530
13917
|
}
|
|
13531
|
-
changedFiles.push(...
|
|
13532
|
-
changedFiles.push(...
|
|
13918
|
+
changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
|
|
13919
|
+
changedFiles.push(...issues.nameIssues.paths);
|
|
13533
13920
|
const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
|
|
13534
13921
|
changedFiles.push(...reservedPreview?.changedFiles ?? []);
|
|
13535
13922
|
return {
|
|
@@ -13547,9 +13934,10 @@ function createBmadChecks() {
|
|
|
13547
13934
|
};
|
|
13548
13935
|
}
|
|
13549
13936
|
if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
|
|
13937
|
+
const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13550
13938
|
const expectedChangedPaths2 = [
|
|
13551
|
-
...
|
|
13552
|
-
...
|
|
13939
|
+
...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
13940
|
+
...before2.nameIssues.paths
|
|
13553
13941
|
];
|
|
13554
13942
|
const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
|
|
13555
13943
|
const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
|
|
@@ -13590,16 +13978,17 @@ function createBmadChecks() {
|
|
|
13590
13978
|
bmadCompanion: update.companion
|
|
13591
13979
|
};
|
|
13592
13980
|
}
|
|
13981
|
+
const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
|
|
13593
13982
|
const expectedChangedPaths = [
|
|
13594
|
-
...
|
|
13595
|
-
...
|
|
13983
|
+
...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
|
|
13984
|
+
...before.nameIssues.paths
|
|
13596
13985
|
];
|
|
13597
13986
|
const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
|
|
13598
13987
|
const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
|
|
13599
13988
|
if (reserved) evicted.unshift(...reserved.details);
|
|
13600
13989
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
|
|
13601
13990
|
if (!install.ok && reserved?.original !== void 0) {
|
|
13602
|
-
|
|
13991
|
+
writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
13603
13992
|
evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
|
|
13604
13993
|
}
|
|
13605
13994
|
if (!install.ok) {
|
|
@@ -13624,6 +14013,58 @@ function createBmadChecks() {
|
|
|
13624
14013
|
};
|
|
13625
14014
|
}
|
|
13626
14015
|
},
|
|
14016
|
+
{
|
|
14017
|
+
id: "bmad.setup-scripts",
|
|
14018
|
+
title: "BMAD ignored installer setup scripts",
|
|
14019
|
+
audit: (ctx) => {
|
|
14020
|
+
if (!isInstalledBmadProject(ctx.repoRoot)) return {
|
|
14021
|
+
id: "bmad.setup-scripts",
|
|
14022
|
+
title: "BMAD ignored installer setup scripts",
|
|
14023
|
+
status: "skip",
|
|
14024
|
+
summary: "No installed BMAD project",
|
|
14025
|
+
details: [],
|
|
14026
|
+
fixable: false
|
|
14027
|
+
};
|
|
14028
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
14029
|
+
const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
|
|
14030
|
+
switch (state.kind) {
|
|
14031
|
+
case "present":
|
|
14032
|
+
return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
|
|
14033
|
+
case "decline":
|
|
14034
|
+
return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
|
|
14035
|
+
case "refuse":
|
|
14036
|
+
return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
|
|
14037
|
+
case "missing":
|
|
14038
|
+
return {
|
|
14039
|
+
id,
|
|
14040
|
+
title,
|
|
14041
|
+
status: "fail",
|
|
14042
|
+
summary: "Ignored BMAD installer scripts are missing",
|
|
14043
|
+
details: state.files.map((path) => relative9(ctx.repoRoot, path)),
|
|
14044
|
+
fixable: true
|
|
14045
|
+
};
|
|
14046
|
+
}
|
|
14047
|
+
},
|
|
14048
|
+
migrate: (ctx, finding2) => {
|
|
14049
|
+
const title = finding2.title, id = finding2.id;
|
|
14050
|
+
if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
|
|
14051
|
+
const state = bmadSetupScriptsState(ctx.repoRoot);
|
|
14052
|
+
if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
|
|
14053
|
+
if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
|
|
14054
|
+
if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
|
|
14055
|
+
const { pkg, files } = state;
|
|
14056
|
+
if (ctx.dryRun) return {
|
|
14057
|
+
id,
|
|
14058
|
+
title,
|
|
14059
|
+
status: "applied",
|
|
14060
|
+
summary: "Would restore ignored scripts from verified pinned bmad-method package",
|
|
14061
|
+
details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
|
|
14062
|
+
changedFiles: files
|
|
14063
|
+
};
|
|
14064
|
+
const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
|
|
14065
|
+
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 };
|
|
14066
|
+
}
|
|
14067
|
+
},
|
|
13627
14068
|
{
|
|
13628
14069
|
id: "bmad.version",
|
|
13629
14070
|
title: "BMAD version currency",
|
|
@@ -13634,7 +14075,7 @@ function createBmadChecks() {
|
|
|
13634
14075
|
id: "bmad.version",
|
|
13635
14076
|
title: "BMAD version currency",
|
|
13636
14077
|
status: "skip",
|
|
13637
|
-
summary: existsSync8(
|
|
14078
|
+
summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
|
|
13638
14079
|
details: [],
|
|
13639
14080
|
fixable: false
|
|
13640
14081
|
};
|
|
@@ -13722,7 +14163,7 @@ function createBmadChecks() {
|
|
|
13722
14163
|
}
|
|
13723
14164
|
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
13724
14165
|
const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
13725
|
-
const manifestPath =
|
|
14166
|
+
const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
13726
14167
|
const manifestSelection = manifestBmadModules(ctx.repoRoot);
|
|
13727
14168
|
if (manifestSelection.status === "invalid") {
|
|
13728
14169
|
return {
|
|
@@ -13820,7 +14261,7 @@ function createBmadChecks() {
|
|
|
13820
14261
|
const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
|
|
13821
14262
|
const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
|
|
13822
14263
|
if (!install.ok) {
|
|
13823
|
-
if (reserved.original !== void 0)
|
|
14264
|
+
if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
|
|
13824
14265
|
return {
|
|
13825
14266
|
id: finding2.id,
|
|
13826
14267
|
title: finding2.title,
|
|
@@ -13855,7 +14296,7 @@ function createBmadChecks() {
|
|
|
13855
14296
|
title: "Supported BMAD CLI projection roots",
|
|
13856
14297
|
audit: (ctx) => {
|
|
13857
14298
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
13858
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
14299
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
13859
14300
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
13860
14301
|
const plan = planSkillRoots(ctx.repoRoot);
|
|
13861
14302
|
const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
|
|
@@ -13877,7 +14318,7 @@ function createBmadChecks() {
|
|
|
13877
14318
|
},
|
|
13878
14319
|
migrate: (ctx, finding2) => {
|
|
13879
14320
|
const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
|
|
13880
|
-
const present = unsupportedNames.filter((name) => existsSync8(
|
|
14321
|
+
const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
|
|
13881
14322
|
const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
|
|
13882
14323
|
const blocked = attestations.filter((entry) => !entry.safe);
|
|
13883
14324
|
if (blocked.length) {
|
|
@@ -13902,7 +14343,7 @@ function createBmadChecks() {
|
|
|
13902
14343
|
};
|
|
13903
14344
|
}
|
|
13904
14345
|
const gitignoreChanges = ensureSupportedCliGitignore(ctx);
|
|
13905
|
-
const removedRoots = attestations.map((entry) =>
|
|
14346
|
+
const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
|
|
13906
14347
|
if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
|
|
13907
14348
|
const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
|
|
13908
14349
|
return {
|
|
@@ -13915,7 +14356,7 @@ function createBmadChecks() {
|
|
|
13915
14356
|
};
|
|
13916
14357
|
}
|
|
13917
14358
|
}
|
|
13918
|
-
].map(withInstallerSkillHandoff);
|
|
14359
|
+
].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
|
|
13919
14360
|
}
|
|
13920
14361
|
function createProjectMomoChecks() {
|
|
13921
14362
|
return [
|
|
@@ -14604,8 +15045,8 @@ var NodeRecipe = class extends Recipe {
|
|
|
14604
15045
|
// src/recipes/ProjectRecipe.ts
|
|
14605
15046
|
init_project();
|
|
14606
15047
|
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
|
|
15048
|
+
import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync23, rmSync as rmSync8 } from "node:fs";
|
|
15049
|
+
import { dirname as dirname18, isAbsolute as isAbsolute10, join as join25, relative as relativePath, resolve as resolve19 } from "node:path";
|
|
14609
15050
|
|
|
14610
15051
|
// src/project/ticketScope.ts
|
|
14611
15052
|
function ticketScope(ticket) {
|
|
@@ -14622,8 +15063,8 @@ init_tree_diff();
|
|
|
14622
15063
|
|
|
14623
15064
|
// src/recipes/NotebookRecipe.ts
|
|
14624
15065
|
init_project();
|
|
14625
|
-
import { existsSync as existsSync17, readFileSync as
|
|
14626
|
-
import { join as
|
|
15066
|
+
import { existsSync as existsSync17, readFileSync as readFileSync22 } from "node:fs";
|
|
15067
|
+
import { join as join24 } from "node:path";
|
|
14627
15068
|
|
|
14628
15069
|
// src/notebook/checks.ts
|
|
14629
15070
|
init_config();
|
|
@@ -14631,8 +15072,8 @@ init_notes();
|
|
|
14631
15072
|
init_output();
|
|
14632
15073
|
init_state();
|
|
14633
15074
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
14634
|
-
import { existsSync as existsSync14, readFileSync as
|
|
14635
|
-
import { join as
|
|
15075
|
+
import { existsSync as existsSync14, readFileSync as readFileSync20 } from "node:fs";
|
|
15076
|
+
import { join as join20 } from "node:path";
|
|
14636
15077
|
var NOTEBOOK_RULE_IDS = [
|
|
14637
15078
|
"notebook.configuration",
|
|
14638
15079
|
"notebook.binding",
|
|
@@ -14649,10 +15090,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
|
|
|
14649
15090
|
return { id: check.id, title: check.title, status, summary, changedFiles, details };
|
|
14650
15091
|
}
|
|
14651
15092
|
function manifestNotebook(repo) {
|
|
14652
|
-
const path =
|
|
15093
|
+
const path = join20(repo, ".project.json");
|
|
14653
15094
|
if (!existsSync14(path)) return null;
|
|
14654
15095
|
try {
|
|
14655
|
-
const parsed = JSON.parse(
|
|
15096
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
14656
15097
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
14657
15098
|
const notebook = parsed.notebook;
|
|
14658
15099
|
return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
|
|
@@ -14881,7 +15322,7 @@ function captureAudit(ctx) {
|
|
|
14881
15322
|
function overviewMigrate(ctx, current) {
|
|
14882
15323
|
if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
|
|
14883
15324
|
if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
|
|
14884
|
-
const pjanglerEntry = ctx.pjanglerRoot ?
|
|
15325
|
+
const pjanglerEntry = ctx.pjanglerRoot ? join20(ctx.pjanglerRoot, "dist", "index.js") : null;
|
|
14885
15326
|
const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
|
|
14886
15327
|
const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
|
|
14887
15328
|
const spawned = spawnSync8(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
|
|
@@ -14933,7 +15374,7 @@ import { resolve as resolve18 } from "node:path";
|
|
|
14933
15374
|
|
|
14934
15375
|
// src/notebook/journal-key.ts
|
|
14935
15376
|
init_types();
|
|
14936
|
-
import { createHash as
|
|
15377
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
14937
15378
|
function validateJournalPeriodKey(period, key) {
|
|
14938
15379
|
if (period === "monthly") {
|
|
14939
15380
|
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 +15393,7 @@ function validateJournalPeriodKey(period, key) {
|
|
|
14952
15393
|
}
|
|
14953
15394
|
function journalNoteOperationId(period, key) {
|
|
14954
15395
|
validateJournalPeriodKey(period, key);
|
|
14955
|
-
const bytes =
|
|
15396
|
+
const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
|
|
14956
15397
|
bytes[6] = bytes[6] & 15 | 80;
|
|
14957
15398
|
bytes[8] = bytes[8] & 63 | 128;
|
|
14958
15399
|
const hex = bytes.toString("hex");
|
|
@@ -15108,9 +15549,9 @@ init_notes();
|
|
|
15108
15549
|
init_state();
|
|
15109
15550
|
init_types();
|
|
15110
15551
|
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
|
|
15552
|
+
import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
|
|
15553
|
+
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";
|
|
15554
|
+
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
15555
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
15115
15556
|
function captureWorkerEnvironment(source, projectSlug) {
|
|
15116
15557
|
const result2 = {
|
|
@@ -15158,7 +15599,7 @@ function bundledSkillCandidates() {
|
|
|
15158
15599
|
const candidates = [];
|
|
15159
15600
|
let cursor = dirname17(fileURLToPath3(import.meta.url));
|
|
15160
15601
|
for (let depth = 0; depth < 8; depth++) {
|
|
15161
|
-
candidates.push(
|
|
15602
|
+
candidates.push(join23(cursor, "dist", "assets", "project-notebook-skill"), join23(cursor, "assets", "project-notebook-skill"));
|
|
15162
15603
|
const parent = dirname17(cursor);
|
|
15163
15604
|
if (parent === cursor) break;
|
|
15164
15605
|
cursor = parent;
|
|
@@ -15176,14 +15617,14 @@ function assertOwnedSkillTree(source) {
|
|
|
15176
15617
|
assertNoSymlinkComponents(source);
|
|
15177
15618
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
15178
15619
|
const walk = (directory) => {
|
|
15179
|
-
const directoryStat =
|
|
15620
|
+
const directoryStat = lstatSync16(directory);
|
|
15180
15621
|
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
|
|
15181
15622
|
if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
|
|
15182
15623
|
if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
|
|
15183
|
-
for (const entry of
|
|
15624
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
15184
15625
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
15185
|
-
const path =
|
|
15186
|
-
const stat =
|
|
15626
|
+
const path = join23(directory, entry.name);
|
|
15627
|
+
const stat = lstatSync16(path);
|
|
15187
15628
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
|
|
15188
15629
|
if (entry.isDirectory()) walk(path);
|
|
15189
15630
|
else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
|
|
@@ -15198,18 +15639,18 @@ function assertOwnedSkillTree(source) {
|
|
|
15198
15639
|
function enumerateSkillPayload(source) {
|
|
15199
15640
|
const result2 = [];
|
|
15200
15641
|
const walk = (directory) => {
|
|
15201
|
-
for (const entry of
|
|
15642
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
|
|
15202
15643
|
if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
|
|
15203
|
-
const path =
|
|
15644
|
+
const path = join23(directory, entry.name);
|
|
15204
15645
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
15205
15646
|
if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
|
|
15206
15647
|
if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
|
|
15207
|
-
const stat =
|
|
15648
|
+
const stat = lstatSync16(path);
|
|
15208
15649
|
if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
|
|
15209
15650
|
if (entry.isDirectory()) walk(path);
|
|
15210
15651
|
else if (entry.isFile()) {
|
|
15211
15652
|
const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
|
|
15212
|
-
result2.push({ path: rel, sha256:
|
|
15653
|
+
result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync21(path)).digest("hex"), mode: expectedMode });
|
|
15213
15654
|
} else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
15214
15655
|
}
|
|
15215
15656
|
};
|
|
@@ -15217,9 +15658,9 @@ function enumerateSkillPayload(source) {
|
|
|
15217
15658
|
return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
15218
15659
|
}
|
|
15219
15660
|
function parsePackedManifest(source) {
|
|
15220
|
-
const manifestPath =
|
|
15661
|
+
const manifestPath = join23(source, "export-manifest.json");
|
|
15221
15662
|
if (!existsSync15(manifestPath)) return null;
|
|
15222
|
-
const value = JSON.parse(
|
|
15663
|
+
const value = JSON.parse(readFileSync21(manifestPath, "utf8"));
|
|
15223
15664
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
|
|
15224
15665
|
const manifest = value;
|
|
15225
15666
|
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 +15669,10 @@ function parsePackedManifest(source) {
|
|
|
15228
15669
|
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
15670
|
if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
|
|
15230
15671
|
paths.add(entry.path);
|
|
15231
|
-
const path =
|
|
15232
|
-
const stat =
|
|
15672
|
+
const path = join23(source, ...entry.path.split("/"));
|
|
15673
|
+
const stat = lstatSync16(path);
|
|
15233
15674
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
|
|
15234
|
-
const actual =
|
|
15675
|
+
const actual = createHash13("sha256").update(readFileSync21(path)).digest("hex");
|
|
15235
15676
|
if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
|
|
15236
15677
|
const actualMode = stat.mode & 511;
|
|
15237
15678
|
const executable = entry.mode === "0755";
|
|
@@ -15243,16 +15684,16 @@ function parsePackedManifest(source) {
|
|
|
15243
15684
|
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
15685
|
const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
15245
15686
|
`;
|
|
15246
|
-
if (!existsSync15(
|
|
15687
|
+
if (!existsSync15(join23(source, "SHA256SUMS")) || readFileSync21(join23(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
|
|
15247
15688
|
return manifest;
|
|
15248
15689
|
}
|
|
15249
15690
|
function expectedPackedSkill() {
|
|
15250
15691
|
for (const candidate of bundledSkillCandidates()) {
|
|
15251
|
-
if (!existsSync15(
|
|
15692
|
+
if (!existsSync15(join23(candidate, "export-manifest.json"))) continue;
|
|
15252
15693
|
assertOwnedSkillTree(candidate);
|
|
15253
15694
|
const manifest = parsePackedManifest(candidate);
|
|
15254
15695
|
if (!manifest) continue;
|
|
15255
|
-
const digest =
|
|
15696
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
15256
15697
|
return { source: candidate, manifest, digest };
|
|
15257
15698
|
}
|
|
15258
15699
|
throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
|
|
@@ -15315,13 +15756,13 @@ function verifyProjectNotebookSkillExport(source) {
|
|
|
15315
15756
|
function resolveProjectNotebookSkillSource(env2 = process.env) {
|
|
15316
15757
|
if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
|
|
15317
15758
|
const explicit = resolve16(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
|
|
15318
|
-
if (!existsSync15(
|
|
15759
|
+
if (!existsSync15(join23(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
|
|
15319
15760
|
verifyProjectNotebookSkillExport(explicit);
|
|
15320
15761
|
return explicit;
|
|
15321
15762
|
}
|
|
15322
15763
|
if (env2.PJ_SKILLS_REGISTRY_ROOT) {
|
|
15323
15764
|
const canonical = resolve16(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
|
|
15324
|
-
if (existsSync15(
|
|
15765
|
+
if (existsSync15(join23(canonical, "SKILL.md"))) {
|
|
15325
15766
|
verifyProjectNotebookSkillExport(canonical);
|
|
15326
15767
|
return canonical;
|
|
15327
15768
|
}
|
|
@@ -15336,7 +15777,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
|
|
|
15336
15777
|
function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
15337
15778
|
let rootLink;
|
|
15338
15779
|
try {
|
|
15339
|
-
rootLink =
|
|
15780
|
+
rootLink = lstatSync16(skillsRoot);
|
|
15340
15781
|
} catch (error) {
|
|
15341
15782
|
if (error.code === "ENOENT") return { state: "absent" };
|
|
15342
15783
|
throw error;
|
|
@@ -15349,15 +15790,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
|
|
|
15349
15790
|
if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
|
|
15350
15791
|
const globalRoot = realpathSync10(skillsRoot);
|
|
15351
15792
|
assertNoSymlinkComponents(globalRoot);
|
|
15352
|
-
const globalStat =
|
|
15793
|
+
const globalStat = lstatSync16(globalRoot);
|
|
15353
15794
|
if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
|
|
15354
15795
|
if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
|
|
15355
15796
|
if (globalStat.mode & 3586) throw new Error("root-mode");
|
|
15356
15797
|
const skillSetsRoot = dirname17(globalRoot);
|
|
15357
|
-
if (
|
|
15798
|
+
if (basename11(globalRoot) !== "global" || basename11(skillSetsRoot) !== "skill-sets") throw new Error("layout");
|
|
15358
15799
|
const checkoutRoot = dirname17(skillSetsRoot);
|
|
15359
|
-
const expectedSource =
|
|
15360
|
-
const linkStat =
|
|
15800
|
+
const expectedSource = join23(checkoutRoot, "all-skills", "project-notebook");
|
|
15801
|
+
const linkStat = lstatSync16(link);
|
|
15361
15802
|
if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
|
|
15362
15803
|
if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
|
|
15363
15804
|
const projectedSource = realpathSync10(link);
|
|
@@ -15391,44 +15832,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
15391
15832
|
const source = input.source ?? resolveProjectNotebookSkillSource(env2);
|
|
15392
15833
|
if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
|
|
15393
15834
|
const manifest = verifyProjectNotebookSkillExport(source);
|
|
15394
|
-
const digest =
|
|
15835
|
+
const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
|
|
15395
15836
|
const home = env2.HOME;
|
|
15396
15837
|
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 =
|
|
15838
|
+
const skillsRoot = join23(home, ".agents", "skills");
|
|
15839
|
+
const link = join23(skillsRoot, "project-notebook");
|
|
15399
15840
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
15400
15841
|
if (probe.state === "adopted") return { installed: false, path: link, digest };
|
|
15401
15842
|
if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
|
|
15402
|
-
const dataRoot = resolve16(env2.XDG_DATA_HOME ||
|
|
15403
|
-
const payload =
|
|
15843
|
+
const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
15844
|
+
const payload = join23(dataRoot, `${PJANGLER_VERSION}-${digest}`);
|
|
15404
15845
|
assertNoSymlinkComponents(dirname17(dataRoot), true);
|
|
15405
|
-
|
|
15846
|
+
mkdirSync10(dataRoot, { recursive: true, mode: 448 });
|
|
15406
15847
|
assertNoSymlinkComponents(dataRoot);
|
|
15407
|
-
const dataStat =
|
|
15848
|
+
const dataStat = lstatSync16(dataRoot);
|
|
15408
15849
|
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
15850
|
chmodSync6(dataRoot, 448);
|
|
15410
15851
|
if (!existsSync15(payload)) {
|
|
15411
|
-
const staging =
|
|
15412
|
-
|
|
15852
|
+
const staging = join23(dataRoot, `.staging-${randomUUID5()}`);
|
|
15853
|
+
mkdirSync10(staging, { recursive: false, mode: 448 });
|
|
15413
15854
|
try {
|
|
15414
15855
|
for (const entry of manifest.files) {
|
|
15415
|
-
const destination =
|
|
15416
|
-
|
|
15417
|
-
copyFileSync3(
|
|
15856
|
+
const destination = join23(staging, ...entry.path.split("/"));
|
|
15857
|
+
mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
|
|
15858
|
+
copyFileSync3(join23(source, ...entry.path.split("/")), destination);
|
|
15418
15859
|
const mode = entry.mode === "0755" ? 493 : 420;
|
|
15419
15860
|
chmodSync6(destination, mode);
|
|
15420
|
-
const fd =
|
|
15861
|
+
const fd = openSync7(destination, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
|
|
15421
15862
|
try {
|
|
15422
|
-
const current =
|
|
15863
|
+
const current = fstatSync4(fd);
|
|
15423
15864
|
if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
|
|
15424
15865
|
fsyncSync4(fd);
|
|
15425
15866
|
} finally {
|
|
15426
|
-
|
|
15867
|
+
closeSync7(fd);
|
|
15427
15868
|
}
|
|
15428
15869
|
}
|
|
15429
|
-
|
|
15870
|
+
writeFileSync13(join23(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
15430
15871
|
`, { mode: 420, flag: "wx" });
|
|
15431
|
-
|
|
15872
|
+
writeFileSync13(join23(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
|
|
15432
15873
|
`, { mode: 420, flag: "wx" });
|
|
15433
15874
|
verifyProjectNotebookSkillExport(staging);
|
|
15434
15875
|
renameSync8(staging, payload);
|
|
@@ -15436,17 +15877,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
15436
15877
|
if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
|
|
15437
15878
|
}
|
|
15438
15879
|
} else {
|
|
15439
|
-
const stat =
|
|
15880
|
+
const stat = lstatSync16(payload);
|
|
15440
15881
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
|
|
15441
15882
|
verifyProjectNotebookSkillExport(payload);
|
|
15442
15883
|
}
|
|
15443
15884
|
assertNoSymlinkComponents(skillsRoot, true);
|
|
15444
|
-
|
|
15885
|
+
mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
|
|
15445
15886
|
assertNoSymlinkComponents(skillsRoot);
|
|
15446
15887
|
let linkExists = existsSync15(link);
|
|
15447
15888
|
if (!linkExists) {
|
|
15448
15889
|
try {
|
|
15449
|
-
|
|
15890
|
+
lstatSync16(link);
|
|
15450
15891
|
linkExists = true;
|
|
15451
15892
|
} catch {
|
|
15452
15893
|
}
|
|
@@ -15463,7 +15904,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
|
|
|
15463
15904
|
repair: REPAIR_COMMAND
|
|
15464
15905
|
}
|
|
15465
15906
|
});
|
|
15466
|
-
const stat =
|
|
15907
|
+
const stat = lstatSync16(link);
|
|
15467
15908
|
if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
|
|
15468
15909
|
const target = realpathSync10(link);
|
|
15469
15910
|
if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
|
|
@@ -15491,15 +15932,15 @@ function collectSupersededPayloads(dataRoot, keep) {
|
|
|
15491
15932
|
const removed = [];
|
|
15492
15933
|
let entries;
|
|
15493
15934
|
try {
|
|
15494
|
-
entries =
|
|
15935
|
+
entries = readdirSync11(dataRoot);
|
|
15495
15936
|
} catch {
|
|
15496
15937
|
return removed;
|
|
15497
15938
|
}
|
|
15498
15939
|
for (const name of entries) {
|
|
15499
15940
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
|
|
15500
|
-
const candidate =
|
|
15941
|
+
const candidate = join23(dataRoot, name);
|
|
15501
15942
|
try {
|
|
15502
|
-
const stat =
|
|
15943
|
+
const stat = lstatSync16(candidate);
|
|
15503
15944
|
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
15504
15945
|
if (kept.has(realpathSync10(candidate))) continue;
|
|
15505
15946
|
rmSync7(candidate, { recursive: true, force: true });
|
|
@@ -15513,8 +15954,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15513
15954
|
const env2 = input.env ?? process.env;
|
|
15514
15955
|
const home = env2.HOME;
|
|
15515
15956
|
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 =
|
|
15957
|
+
const skillsRoot = join23(home, ".agents", "skills");
|
|
15958
|
+
const link = join23(skillsRoot, "project-notebook");
|
|
15518
15959
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
15519
15960
|
if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
|
|
15520
15961
|
if (probe.state !== "declined") {
|
|
@@ -15527,8 +15968,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15527
15968
|
assertOwnedSkillTree(source);
|
|
15528
15969
|
const present = [];
|
|
15529
15970
|
const walk = (directory) => {
|
|
15530
|
-
for (const entry of
|
|
15531
|
-
const path =
|
|
15971
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true })) {
|
|
15972
|
+
const path = join23(directory, entry.name);
|
|
15532
15973
|
const rel = relative15(source, path).split(sep7).join("/");
|
|
15533
15974
|
if (entry.isDirectory()) {
|
|
15534
15975
|
walk(path);
|
|
@@ -15542,11 +15983,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15542
15983
|
walk(source);
|
|
15543
15984
|
const stale = present.filter((rel) => !wanted.has(rel)).sort();
|
|
15544
15985
|
const changed = expected.manifest.files.filter((entry) => {
|
|
15545
|
-
const path =
|
|
15986
|
+
const path = join23(source, ...entry.path.split("/"));
|
|
15546
15987
|
if (!existsSync15(path)) return true;
|
|
15547
|
-
const stat =
|
|
15988
|
+
const stat = lstatSync16(path);
|
|
15548
15989
|
if (!stat.isFile()) return true;
|
|
15549
|
-
if (
|
|
15990
|
+
if (createHash13("sha256").update(readFileSync21(path)).digest("hex") !== entry.sha256) return true;
|
|
15550
15991
|
const mode = stat.mode & 511;
|
|
15551
15992
|
const executable = entry.mode === "0755";
|
|
15552
15993
|
return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
|
|
@@ -15561,15 +16002,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15561
16002
|
summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
|
|
15562
16003
|
source,
|
|
15563
16004
|
drift: probe.block.details,
|
|
15564
|
-
changed_files: affected.map((rel) =>
|
|
16005
|
+
changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
|
|
15565
16006
|
};
|
|
15566
16007
|
}
|
|
15567
|
-
for (const rel of stale) rmSync7(
|
|
16008
|
+
for (const rel of stale) rmSync7(join23(source, ...rel.split("/")), { force: true });
|
|
15568
16009
|
for (const rel of changed) {
|
|
15569
|
-
const destination =
|
|
16010
|
+
const destination = join23(source, ...rel.split("/"));
|
|
15570
16011
|
const entry = expected.manifest.files.find((item) => item.path === rel);
|
|
15571
|
-
|
|
15572
|
-
copyFileSync3(
|
|
16012
|
+
mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
|
|
16013
|
+
copyFileSync3(join23(expected.source, ...rel.split("/")), destination);
|
|
15573
16014
|
chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
|
|
15574
16015
|
}
|
|
15575
16016
|
verifyProjectNotebookSkillExport(source);
|
|
@@ -15578,21 +16019,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
|
|
|
15578
16019
|
summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
|
|
15579
16020
|
source,
|
|
15580
16021
|
drift: probe.block.details,
|
|
15581
|
-
changed_files: affected.map((rel) =>
|
|
16022
|
+
changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
|
|
15582
16023
|
};
|
|
15583
16024
|
}
|
|
15584
16025
|
function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
15585
16026
|
const home = env2.HOME;
|
|
15586
16027
|
if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
|
|
15587
|
-
const skillsRoot =
|
|
15588
|
-
const link =
|
|
16028
|
+
const skillsRoot = join23(home, ".agents", "skills");
|
|
16029
|
+
const link = join23(skillsRoot, "project-notebook");
|
|
15589
16030
|
try {
|
|
15590
16031
|
const expected = expectedPackedSkill();
|
|
15591
|
-
const dataRoot = resolve16(env2.XDG_DATA_HOME ||
|
|
15592
|
-
const expectedPayload =
|
|
16032
|
+
const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
|
|
16033
|
+
const expectedPayload = join23(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
|
|
15593
16034
|
const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
|
|
15594
16035
|
if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
|
|
15595
|
-
const stat =
|
|
16036
|
+
const stat = lstatSync16(link);
|
|
15596
16037
|
if (!stat.isSymbolicLink()) {
|
|
15597
16038
|
return {
|
|
15598
16039
|
skill_installed: false,
|
|
@@ -15629,8 +16070,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
|
|
|
15629
16070
|
}
|
|
15630
16071
|
}
|
|
15631
16072
|
function projectorArguments(source, command, input) {
|
|
15632
|
-
const script =
|
|
15633
|
-
const args = [script, command, "--master",
|
|
16073
|
+
const script = join23(source, "scripts", "project-hooks.py");
|
|
16074
|
+
const args = [script, command, "--master", join23(source, "hooks", "hooks.master.json"), "--fragment", join23(source, "hooks", "claude.settings.json"), "--target", input.target];
|
|
15634
16075
|
if (command === "check") args.push("--json");
|
|
15635
16076
|
else if (input.stateHome) args.push("--state-home", input.stateHome);
|
|
15636
16077
|
return args;
|
|
@@ -15652,7 +16093,7 @@ function checkProjectNotebookHooks(input = {}) {
|
|
|
15652
16093
|
verifyProjectNotebookSkillExport(source);
|
|
15653
16094
|
const home = env2.HOME;
|
|
15654
16095
|
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 ??
|
|
16096
|
+
const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
|
|
15656
16097
|
const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
15657
16098
|
if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
|
|
15658
16099
|
try {
|
|
@@ -15670,14 +16111,14 @@ function installProjectNotebookIntegration(input = {}) {
|
|
|
15670
16111
|
const source = realpathSync10(skill.path);
|
|
15671
16112
|
const home = env2.HOME;
|
|
15672
16113
|
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 ||
|
|
16114
|
+
const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
|
|
16115
|
+
const stateHome = resolve16(env2.XDG_STATE_HOME || join23(home, ".local", "state"));
|
|
15675
16116
|
const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
|
|
15676
16117
|
if (result2.status !== 0) {
|
|
15677
16118
|
if (skill.installed) {
|
|
15678
16119
|
try {
|
|
15679
|
-
const stat =
|
|
15680
|
-
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source)
|
|
16120
|
+
const stat = lstatSync16(skill.path);
|
|
16121
|
+
if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
|
|
15681
16122
|
} catch {
|
|
15682
16123
|
}
|
|
15683
16124
|
}
|
|
@@ -15722,10 +16163,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
|
|
|
15722
16163
|
const root = parse3(absolute).root;
|
|
15723
16164
|
let cursor = root;
|
|
15724
16165
|
for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
|
|
15725
|
-
cursor =
|
|
16166
|
+
cursor = join23(cursor, component);
|
|
15726
16167
|
let stat;
|
|
15727
16168
|
try {
|
|
15728
|
-
stat =
|
|
16169
|
+
stat = lstatSync16(cursor);
|
|
15729
16170
|
} catch (error) {
|
|
15730
16171
|
if (allowMissing && error.code === "ENOENT") return;
|
|
15731
16172
|
throw error;
|
|
@@ -15744,18 +16185,18 @@ function readHookPayload(input) {
|
|
|
15744
16185
|
if (rel === ".." || rel.startsWith(`..${sep7}`) || rel.startsWith(sep7)) throw new NotebookError("INVALID_INPUT", "Hook payload file is outside Notebook XDG state");
|
|
15745
16186
|
assertNoSymlinkComponents(root);
|
|
15746
16187
|
assertNoSymlinkComponents(path);
|
|
15747
|
-
const before =
|
|
16188
|
+
const before = lstatSync16(path);
|
|
15748
16189
|
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
16190
|
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
16191
|
if (before.size > input.maxBytes) throw new NotebookError("INVALID_INPUT", "Hook payload file exceeds its configured ceiling");
|
|
15751
|
-
const fd =
|
|
16192
|
+
const fd = openSync7(path, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
|
|
15752
16193
|
try {
|
|
15753
|
-
const opened =
|
|
16194
|
+
const opened = fstatSync4(fd);
|
|
15754
16195
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", "Hook payload file changed while opening");
|
|
15755
16196
|
const value = readBoundedHookStdin(input.maxBytes, fd);
|
|
15756
16197
|
return { payload: parsePayload(value, input.maxBytes), bytes: Buffer.byteLength(value, "utf8") };
|
|
15757
16198
|
} finally {
|
|
15758
|
-
|
|
16199
|
+
closeSync7(fd);
|
|
15759
16200
|
}
|
|
15760
16201
|
}
|
|
15761
16202
|
var ALLOWED_CLIENT_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -16482,17 +16923,17 @@ init_remote_mutation_journal();
|
|
|
16482
16923
|
function integrationEnvironment(module, ctx) {
|
|
16483
16924
|
const env2 = { ...module.environment, HOME: ctx.homeDir };
|
|
16484
16925
|
if (module.environment.HOME !== ctx.homeDir) {
|
|
16485
|
-
env2.XDG_DATA_HOME =
|
|
16486
|
-
env2.XDG_STATE_HOME =
|
|
16487
|
-
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS =
|
|
16926
|
+
env2.XDG_DATA_HOME = join24(ctx.homeDir, ".local", "share");
|
|
16927
|
+
env2.XDG_STATE_HOME = join24(ctx.homeDir, ".local", "state");
|
|
16928
|
+
env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join24(ctx.homeDir, ".claude", "settings.json");
|
|
16488
16929
|
}
|
|
16489
16930
|
return env2;
|
|
16490
16931
|
}
|
|
16491
16932
|
function resolvedForPlan(plan) {
|
|
16492
|
-
const manifestPath =
|
|
16933
|
+
const manifestPath = join24(plan.project.repo_path, ".project.json");
|
|
16493
16934
|
let manifest = plan.manifest;
|
|
16494
16935
|
if (existsSync17(manifestPath)) {
|
|
16495
|
-
const parsed = JSON.parse(
|
|
16936
|
+
const parsed = JSON.parse(readFileSync22(manifestPath, "utf8"));
|
|
16496
16937
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
|
|
16497
16938
|
manifest = parsed;
|
|
16498
16939
|
}
|
|
@@ -16669,7 +17110,7 @@ function unsafeToRemove(targetDir) {
|
|
|
16669
17110
|
while (!seen.has(cursor)) {
|
|
16670
17111
|
seen.add(cursor);
|
|
16671
17112
|
try {
|
|
16672
|
-
const stat =
|
|
17113
|
+
const stat = lstatSync17(cursor);
|
|
16673
17114
|
if (stat.isSymbolicLink()) {
|
|
16674
17115
|
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
17116
|
}
|
|
@@ -16697,12 +17138,12 @@ function publicMigration(report) {
|
|
|
16697
17138
|
};
|
|
16698
17139
|
}
|
|
16699
17140
|
function hasGitRepository(runtime, targetDir) {
|
|
16700
|
-
if (!existsSync18(
|
|
17141
|
+
if (!existsSync18(join25(targetDir, ".git"))) return false;
|
|
16701
17142
|
return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
|
|
16702
17143
|
}
|
|
16703
17144
|
function refreshPlanFromCanonicalManifest(plan) {
|
|
16704
|
-
const manifestPath =
|
|
16705
|
-
const manifest = JSON.parse(
|
|
17145
|
+
const manifestPath = join25(plan.project.repo_path, ".project.json");
|
|
17146
|
+
const manifest = JSON.parse(readFileSync23(manifestPath, "utf8"));
|
|
16706
17147
|
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
16707
17148
|
throw new Error(`${manifestPath} must contain a JSON object`);
|
|
16708
17149
|
}
|
|
@@ -16933,7 +17374,7 @@ var ProjectRecipe = class extends Recipe {
|
|
|
16933
17374
|
if (hasGitRepository(this.runtime, targetDir)) {
|
|
16934
17375
|
phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
|
|
16935
17376
|
} else {
|
|
16936
|
-
const gitPath =
|
|
17377
|
+
const gitPath = join25(targetDir, ".git");
|
|
16937
17378
|
for (const { args, label, options } of [
|
|
16938
17379
|
{ args: ["init", "--initial-branch=main"], label: "git init" },
|
|
16939
17380
|
{ args: ["add", "-A"], label: "git add" },
|
|
@@ -17144,12 +17585,12 @@ var ProjectRecipe = class extends Recipe {
|
|
|
17144
17585
|
};
|
|
17145
17586
|
|
|
17146
17587
|
// src/recipes/registry.ts
|
|
17147
|
-
import { readFileSync as
|
|
17148
|
-
import { join as
|
|
17588
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
17589
|
+
import { join as join26 } from "node:path";
|
|
17149
17590
|
function projectWaivers(repoRoot) {
|
|
17150
17591
|
let waive;
|
|
17151
17592
|
try {
|
|
17152
|
-
waive = JSON.parse(
|
|
17593
|
+
waive = JSON.parse(readFileSync24(join26(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
|
|
17153
17594
|
} catch {
|
|
17154
17595
|
return /* @__PURE__ */ new Map();
|
|
17155
17596
|
}
|
|
@@ -17490,8 +17931,8 @@ var recipeRegistry = new RecipeRegistry([
|
|
|
17490
17931
|
|
|
17491
17932
|
// src/commands/AgentHooksCommands.ts
|
|
17492
17933
|
import { homedir as homedir10 } from "node:os";
|
|
17493
|
-
import { join as
|
|
17494
|
-
import { existsSync as existsSync19, cpSync, mkdirSync as
|
|
17934
|
+
import { join as join27, dirname as dirname19 } from "node:path";
|
|
17935
|
+
import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync14 } from "node:fs";
|
|
17495
17936
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
17496
17937
|
init_project();
|
|
17497
17938
|
import { parse as parseToml4 } from "smol-toml";
|
|
@@ -17504,16 +17945,16 @@ function resolveTemplateRoot() {
|
|
|
17504
17945
|
try {
|
|
17505
17946
|
let dir = dirname19(fileURLToPath4(import.meta.url));
|
|
17506
17947
|
for (let i = 0; i < 8; i++) {
|
|
17507
|
-
candidates.push(
|
|
17948
|
+
candidates.push(join27(dir, "templates", "commonproject", "template"));
|
|
17508
17949
|
const parent = dirname19(dir);
|
|
17509
17950
|
if (parent === dir) break;
|
|
17510
17951
|
dir = parent;
|
|
17511
17952
|
}
|
|
17512
17953
|
} catch {
|
|
17513
17954
|
}
|
|
17514
|
-
candidates.push(
|
|
17955
|
+
candidates.push(join27(homedir10(), "code", "pjangler", "templates", "commonproject", "template"));
|
|
17515
17956
|
for (const c of candidates) {
|
|
17516
|
-
if (existsSync19(
|
|
17957
|
+
if (existsSync19(join27(c, ".agents", "hooks", "hooks.master.json"))) return c;
|
|
17517
17958
|
}
|
|
17518
17959
|
throw new Error(
|
|
17519
17960
|
"Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
|
|
@@ -17539,15 +17980,15 @@ var CopyAgentHooksTree = class extends Command {
|
|
|
17539
17980
|
const created = [];
|
|
17540
17981
|
const skipped = [];
|
|
17541
17982
|
for (const { rel, dir } of items) {
|
|
17542
|
-
const src =
|
|
17543
|
-
const dest =
|
|
17983
|
+
const src = join27(templateRoot, rel);
|
|
17984
|
+
const dest = join27(this.context.targetDir, rel);
|
|
17544
17985
|
if (!existsSync19(src)) continue;
|
|
17545
17986
|
if (existsSync19(dest) && !this.context.force) {
|
|
17546
17987
|
skipped.push(rel);
|
|
17547
17988
|
continue;
|
|
17548
17989
|
}
|
|
17549
17990
|
if (!this.context.dryRun) {
|
|
17550
|
-
|
|
17991
|
+
mkdirSync11(dirname19(dest), { recursive: true });
|
|
17551
17992
|
cpSync(src, dest, { recursive: dir, force: true });
|
|
17552
17993
|
}
|
|
17553
17994
|
created.push(rel);
|
|
@@ -17568,14 +18009,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
|
|
|
17568
18009
|
if (!resolveAgentHooksLayer2()) {
|
|
17569
18010
|
return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
|
|
17570
18011
|
}
|
|
17571
|
-
const misePath =
|
|
18012
|
+
const misePath = join27(this.context.targetDir, "mise.toml");
|
|
17572
18013
|
if (!existsSync19(misePath)) {
|
|
17573
18014
|
return {
|
|
17574
18015
|
success: false,
|
|
17575
18016
|
message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
|
|
17576
18017
|
};
|
|
17577
18018
|
}
|
|
17578
|
-
let content =
|
|
18019
|
+
let content = readFileSync25(misePath, "utf8");
|
|
17579
18020
|
if (content.includes(_WireMiseAgentHooks.MARKER)) {
|
|
17580
18021
|
return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
|
|
17581
18022
|
}
|
|
@@ -17659,7 +18100,7 @@ ${leaveBlock}`);
|
|
|
17659
18100
|
if (!verdict.ok) {
|
|
17660
18101
|
return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
|
|
17661
18102
|
}
|
|
17662
|
-
if (!this.context.dryRun)
|
|
18103
|
+
if (!this.context.dryRun) writeFileSync14(misePath, content);
|
|
17663
18104
|
if (wiredHooks) {
|
|
17664
18105
|
return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
|
|
17665
18106
|
}
|
|
@@ -17790,7 +18231,7 @@ if __name__ == "__main__":
|
|
|
17790
18231
|
|
|
17791
18232
|
// src/commands/AddMiseCodegraphScript.ts
|
|
17792
18233
|
import { chmodSync as chmodSync7 } from "fs";
|
|
17793
|
-
import { join as
|
|
18234
|
+
import { join as join28 } from "path";
|
|
17794
18235
|
var AddMiseCodegraphScript = class extends Command {
|
|
17795
18236
|
async invoke() {
|
|
17796
18237
|
const filePath = ".mise/scripts/codegraph.sh";
|
|
@@ -17844,7 +18285,7 @@ fi
|
|
|
17844
18285
|
`;
|
|
17845
18286
|
this.writeFile(filePath, content);
|
|
17846
18287
|
if (!this.context.dryRun) {
|
|
17847
|
-
chmodSync7(
|
|
18288
|
+
chmodSync7(join28(this.context.targetDir, filePath), 493);
|
|
17848
18289
|
}
|
|
17849
18290
|
return {
|
|
17850
18291
|
success: true,
|
|
@@ -17882,12 +18323,12 @@ SECRET_KEY=""
|
|
|
17882
18323
|
// src/parity/index.ts
|
|
17883
18324
|
import { existsSync as existsSync20 } from "node:fs";
|
|
17884
18325
|
import { homedir as homedir11 } from "node:os";
|
|
17885
|
-
import { dirname as dirname20, join as
|
|
18326
|
+
import { dirname as dirname20, join as join29, resolve as resolve20 } from "node:path";
|
|
17886
18327
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
17887
18328
|
function resolvePjanglerRoot2() {
|
|
17888
18329
|
let dir = dirname20(fileURLToPath5(import.meta.url));
|
|
17889
18330
|
while (dir !== dirname20(dir)) {
|
|
17890
|
-
if (existsSync20(
|
|
18331
|
+
if (existsSync20(join29(dir, "package.json")) && existsSync20(join29(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
17891
18332
|
dir = dirname20(dir);
|
|
17892
18333
|
}
|
|
17893
18334
|
return resolve20(process.cwd());
|
|
@@ -18079,13 +18520,13 @@ init_project();
|
|
|
18079
18520
|
// src/project/info.ts
|
|
18080
18521
|
init_boardUrl();
|
|
18081
18522
|
init_project();
|
|
18082
|
-
import { readFileSync as
|
|
18083
|
-
import { join as
|
|
18523
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
18524
|
+
import { join as join30, resolve as resolve21 } from "node:path";
|
|
18084
18525
|
function readManifest2(root) {
|
|
18085
|
-
const path =
|
|
18526
|
+
const path = join30(root, ".project.json");
|
|
18086
18527
|
let manifest;
|
|
18087
18528
|
try {
|
|
18088
|
-
const value = JSON.parse(
|
|
18529
|
+
const value = JSON.parse(readFileSync26(path, "utf8"));
|
|
18089
18530
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
|
|
18090
18531
|
manifest = value;
|
|
18091
18532
|
} catch (error) {
|
|
@@ -18126,7 +18567,7 @@ function readProjectInfo(input = {}) {
|
|
|
18126
18567
|
ok: true,
|
|
18127
18568
|
project_id: projectId,
|
|
18128
18569
|
repo_path: resolve21(root),
|
|
18129
|
-
manifest_path:
|
|
18570
|
+
manifest_path: join30(resolve21(root), ".project.json"),
|
|
18130
18571
|
manifest,
|
|
18131
18572
|
registry: { location, status: "indexed" }
|
|
18132
18573
|
};
|
|
@@ -18175,20 +18616,20 @@ init_project();
|
|
|
18175
18616
|
init_version();
|
|
18176
18617
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
18177
18618
|
import {
|
|
18178
|
-
closeSync as
|
|
18619
|
+
closeSync as closeSync8,
|
|
18179
18620
|
existsSync as existsSync21,
|
|
18180
|
-
fchmodSync as
|
|
18621
|
+
fchmodSync as fchmodSync5,
|
|
18181
18622
|
fsyncSync as fsyncSync5,
|
|
18182
|
-
lstatSync as
|
|
18183
|
-
mkdirSync as
|
|
18184
|
-
openSync as
|
|
18185
|
-
readFileSync as
|
|
18623
|
+
lstatSync as lstatSync18,
|
|
18624
|
+
mkdirSync as mkdirSync12,
|
|
18625
|
+
openSync as openSync8,
|
|
18626
|
+
readFileSync as readFileSync27,
|
|
18186
18627
|
renameSync as renameSync9,
|
|
18187
|
-
unlinkSync as
|
|
18188
|
-
writeFileSync as
|
|
18628
|
+
unlinkSync as unlinkSync10,
|
|
18629
|
+
writeFileSync as writeFileSync15
|
|
18189
18630
|
} from "node:fs";
|
|
18190
18631
|
import { homedir as homedir12 } from "node:os";
|
|
18191
|
-
import { dirname as dirname21, join as
|
|
18632
|
+
import { dirname as dirname21, join as join31, resolve as resolve22 } from "node:path";
|
|
18192
18633
|
import YAML7 from "yaml";
|
|
18193
18634
|
var DEAD_AGENT_IDS = ["coachingagentframework-pm"];
|
|
18194
18635
|
function isRecord6(value) {
|
|
@@ -18196,7 +18637,7 @@ function isRecord6(value) {
|
|
|
18196
18637
|
}
|
|
18197
18638
|
function hermesAgentsRegistryPath(env2 = process.env, home = homedir12()) {
|
|
18198
18639
|
const override = env2.HERMES_AGENTS_REGISTRY?.trim();
|
|
18199
|
-
return override ||
|
|
18640
|
+
return override || join31(home, ".hermes", "agents-registry.yaml");
|
|
18200
18641
|
}
|
|
18201
18642
|
function planeApiKeyFor(workspace, env2, home) {
|
|
18202
18643
|
return resolvePlaneApiKey(workspace, env2, home) ?? resolvePlaneApiKey(DEFAULT_PLANE_WORKSPACE, env2, home);
|
|
@@ -18207,7 +18648,7 @@ async function fetchPlaneBoards(workspace, env2 = process.env, home = homedir12(
|
|
|
18207
18648
|
const apiKey = planeApiKeyFor(workspace, env2, home);
|
|
18208
18649
|
if (!apiKey) {
|
|
18209
18650
|
throw new BoardError(
|
|
18210
|
-
`no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() ||
|
|
18651
|
+
`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
18652
|
);
|
|
18212
18653
|
}
|
|
18213
18654
|
const base = planeBase(env2, home);
|
|
@@ -18256,7 +18697,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
|
|
|
18256
18697
|
const apiKey = planeApiKeyFor(workspace, env2, home);
|
|
18257
18698
|
if (!apiKey) {
|
|
18258
18699
|
throw new BoardError(
|
|
18259
|
-
`no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() ||
|
|
18700
|
+
`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
18701
|
);
|
|
18261
18702
|
}
|
|
18262
18703
|
const base = planeBase(env2, home);
|
|
@@ -18290,7 +18731,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
|
|
|
18290
18731
|
}
|
|
18291
18732
|
}
|
|
18292
18733
|
function trelloCredential(name, env2, home) {
|
|
18293
|
-
const fleetEnv = env2.HERMES_FLEET_ENV?.trim() ||
|
|
18734
|
+
const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env");
|
|
18294
18735
|
const raw = env2[name]?.trim() || dotenvValue(fleetEnv, name)?.trim();
|
|
18295
18736
|
return raw ? resolveSecretValue(raw) : void 0;
|
|
18296
18737
|
}
|
|
@@ -18328,7 +18769,7 @@ async function fetchTrelloBoard(boardId, env2 = process.env, home = homedir12())
|
|
|
18328
18769
|
};
|
|
18329
18770
|
}
|
|
18330
18771
|
function readHermesAgentBoards(path) {
|
|
18331
|
-
const document = YAML7.parseDocument(
|
|
18772
|
+
const document = YAML7.parseDocument(readFileSync27(path, "utf8"));
|
|
18332
18773
|
if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
|
|
18333
18774
|
const parsed = document.toJS();
|
|
18334
18775
|
const agents = isRecord6(parsed) && isRecord6(parsed.agents) ? parsed.agents : {};
|
|
@@ -18359,11 +18800,11 @@ function matchRegistrySlug(agent, registry) {
|
|
|
18359
18800
|
}
|
|
18360
18801
|
function readManifestBoard(repoPath) {
|
|
18361
18802
|
if (!repoPath) return void 0;
|
|
18362
|
-
const path =
|
|
18803
|
+
const path = join31(repoPath, ".project.json");
|
|
18363
18804
|
if (!existsSync21(path)) return void 0;
|
|
18364
18805
|
let parsed;
|
|
18365
18806
|
try {
|
|
18366
|
-
parsed = JSON.parse(
|
|
18807
|
+
parsed = JSON.parse(readFileSync27(path, "utf8"));
|
|
18367
18808
|
} catch {
|
|
18368
18809
|
return void 0;
|
|
18369
18810
|
}
|
|
@@ -18382,32 +18823,32 @@ function yamlStyleOf(original) {
|
|
|
18382
18823
|
return { indentSeq: !/\n([ \t]*)[^\s#][^\n]*:[ \t]*\n\1- /.test(original), lineWidth: 0 };
|
|
18383
18824
|
}
|
|
18384
18825
|
function atomicWrite(path, text2) {
|
|
18385
|
-
const mode = existsSync21(path) ?
|
|
18386
|
-
|
|
18826
|
+
const mode = existsSync21(path) ? lstatSync18(path).mode & 511 : 384;
|
|
18827
|
+
mkdirSync12(dirname21(path), { recursive: true });
|
|
18387
18828
|
const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
18388
18829
|
let fd;
|
|
18389
18830
|
try {
|
|
18390
|
-
fd =
|
|
18391
|
-
|
|
18392
|
-
|
|
18831
|
+
fd = openSync8(temp, "wx", mode);
|
|
18832
|
+
writeFileSync15(fd, text2, "utf8");
|
|
18833
|
+
fchmodSync5(fd, mode);
|
|
18393
18834
|
fsyncSync5(fd);
|
|
18394
|
-
|
|
18835
|
+
closeSync8(fd);
|
|
18395
18836
|
fd = void 0;
|
|
18396
18837
|
renameSync9(temp, path);
|
|
18397
18838
|
} catch (error) {
|
|
18398
18839
|
if (fd !== void 0) try {
|
|
18399
|
-
|
|
18840
|
+
closeSync8(fd);
|
|
18400
18841
|
} catch {
|
|
18401
18842
|
}
|
|
18402
18843
|
try {
|
|
18403
|
-
|
|
18844
|
+
unlinkSync10(temp);
|
|
18404
18845
|
} catch {
|
|
18405
18846
|
}
|
|
18406
18847
|
throw error;
|
|
18407
18848
|
}
|
|
18408
18849
|
}
|
|
18409
18850
|
function applyHermesIdentifiers(path, identifiers, deletions) {
|
|
18410
|
-
const original =
|
|
18851
|
+
const original = readFileSync27(path, "utf8");
|
|
18411
18852
|
const document = YAML7.parseDocument(original);
|
|
18412
18853
|
if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
|
|
18413
18854
|
const before = document.toJS();
|
|
@@ -18863,11 +19304,11 @@ function formatIdentityReport(report) {
|
|
|
18863
19304
|
return lines.join("\n");
|
|
18864
19305
|
}
|
|
18865
19306
|
function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
|
|
18866
|
-
const path =
|
|
19307
|
+
const path = join31(repoPath, ".project.json");
|
|
18867
19308
|
if (!existsSync21(path)) return void 0;
|
|
18868
19309
|
let existing = {};
|
|
18869
19310
|
try {
|
|
18870
|
-
const parsed = JSON.parse(
|
|
19311
|
+
const parsed = JSON.parse(readFileSync27(path, "utf8"));
|
|
18871
19312
|
if (isRecord6(parsed)) existing = parsed;
|
|
18872
19313
|
} catch {
|
|
18873
19314
|
throw new BoardError(`${path} is not readable JSON; fix it before linking a board`);
|
|
@@ -18892,7 +19333,7 @@ function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
|
|
|
18892
19333
|
};
|
|
18893
19334
|
const text2 = `${JSON.stringify(next, null, 2)}
|
|
18894
19335
|
`;
|
|
18895
|
-
if (
|
|
19336
|
+
if (readFileSync27(path, "utf8") === text2) return void 0;
|
|
18896
19337
|
atomicWrite(path, text2);
|
|
18897
19338
|
return path;
|
|
18898
19339
|
}
|
|
@@ -18904,8 +19345,8 @@ async function linkProjectBoard(options) {
|
|
|
18904
19345
|
const project = getProject(registry, options.slug);
|
|
18905
19346
|
const boardId = options.boardId.trim();
|
|
18906
19347
|
if (!boardId) throw new BoardError("a board id is required");
|
|
18907
|
-
const manifestFile =
|
|
18908
|
-
const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(
|
|
19348
|
+
const manifestFile = join31(project.repo_path, ".project.json");
|
|
19349
|
+
const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(readFileSync27(manifestFile, "utf8")).ticket_provider : void 0;
|
|
18909
19350
|
const current = project.ticket_provider;
|
|
18910
19351
|
const type = (current.type || "plane").trim().toLowerCase();
|
|
18911
19352
|
const workspace = (options.workspace ?? current.workspace ?? DEFAULT_PLANE_WORKSPACE).trim();
|
|
@@ -18956,10 +19397,10 @@ async function linkProjectBoard(options) {
|
|
|
18956
19397
|
if (apply) {
|
|
18957
19398
|
registry.projects[project.slug] = { ...project, ticket_provider: { ...current, ...block }, updated_at: now };
|
|
18958
19399
|
if (isRegistryServiceLocation(registryPath)) {
|
|
18959
|
-
const beforeManifest = existsSync21(manifestFile) ?
|
|
19400
|
+
const beforeManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
|
|
18960
19401
|
saveProjectRegistry(registry, registryPath);
|
|
18961
19402
|
manifestPath = manifestFile;
|
|
18962
|
-
const afterManifest = existsSync21(manifestFile) ?
|
|
19403
|
+
const afterManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
|
|
18963
19404
|
if (beforeManifest !== afterManifest) changedFiles.push(manifestFile);
|
|
18964
19405
|
} else {
|
|
18965
19406
|
manifestPath = writeManifestTicketProvider(project.repo_path, block, expectedProvider);
|
|
@@ -18968,7 +19409,7 @@ async function linkProjectBoard(options) {
|
|
|
18968
19409
|
changedFiles.push(registryPath);
|
|
18969
19410
|
}
|
|
18970
19411
|
} else {
|
|
18971
|
-
manifestPath = existsSync21(
|
|
19412
|
+
manifestPath = existsSync21(join31(project.repo_path, ".project.json")) ? join31(project.repo_path, ".project.json") : void 0;
|
|
18972
19413
|
}
|
|
18973
19414
|
return {
|
|
18974
19415
|
ok: true,
|
|
@@ -18992,9 +19433,9 @@ async function linkProjectBoard(options) {
|
|
|
18992
19433
|
// src/project/rename.ts
|
|
18993
19434
|
init_boardUrl();
|
|
18994
19435
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
18995
|
-
import { existsSync as existsSync22, readdirSync as
|
|
19436
|
+
import { existsSync as existsSync22, readdirSync as readdirSync12, readFileSync as readFileSync28, realpathSync as realpathSync11, statSync as statSync6 } from "node:fs";
|
|
18996
19437
|
import { homedir as homedir13 } from "node:os";
|
|
18997
|
-
import { isAbsolute as isAbsolute11, join as
|
|
19438
|
+
import { isAbsolute as isAbsolute11, join as join32, relative as relative16 } from "node:path";
|
|
18998
19439
|
import YAML8 from "yaml";
|
|
18999
19440
|
init_project();
|
|
19000
19441
|
init_registryClient();
|
|
@@ -19015,7 +19456,7 @@ function message(error) {
|
|
|
19015
19456
|
function readJsonObject(path) {
|
|
19016
19457
|
let parsed;
|
|
19017
19458
|
try {
|
|
19018
|
-
parsed = JSON.parse(
|
|
19459
|
+
parsed = JSON.parse(readFileSync28(path, "utf8"));
|
|
19019
19460
|
} catch {
|
|
19020
19461
|
throw new RenameError("manifest_invalid", `${path} is not readable JSON; fix it before renaming`);
|
|
19021
19462
|
}
|
|
@@ -19068,7 +19509,7 @@ async function renameProject(options) {
|
|
|
19068
19509
|
let from;
|
|
19069
19510
|
if (existing && oldInput) {
|
|
19070
19511
|
try {
|
|
19071
|
-
const candidate = readJsonObject(
|
|
19512
|
+
const candidate = readJsonObject(join32(existing.repo_path, ".project.json")).renamed_from;
|
|
19072
19513
|
if (isRecord7(candidate)) from = candidate;
|
|
19073
19514
|
} catch {
|
|
19074
19515
|
}
|
|
@@ -19087,7 +19528,7 @@ async function renameProject(options) {
|
|
|
19087
19528
|
const isPlane = type === "plane";
|
|
19088
19529
|
const workspace = isPlane ? String(provider.workspace ?? "").trim() || DEFAULT_PLANE_WORKSPACE : String(provider.workspace ?? "").trim();
|
|
19089
19530
|
const repoPath = record3.repo_path;
|
|
19090
|
-
const manifestPath =
|
|
19531
|
+
const manifestPath = join32(repoPath, ".project.json");
|
|
19091
19532
|
const manifestExists = existsSync22(manifestPath);
|
|
19092
19533
|
if (service && !manifestExists) {
|
|
19093
19534
|
throw new RenameError("manifest_missing", `${manifestPath} is missing; the registry service indexes manifests, so there is nothing to rename`);
|
|
@@ -19232,7 +19673,7 @@ async function renameProject(options) {
|
|
|
19232
19673
|
return { status: "done", detail, files: service ? [] : [registryPath] };
|
|
19233
19674
|
};
|
|
19234
19675
|
const roleStep = async (doApply) => {
|
|
19235
|
-
const hermesDir =
|
|
19676
|
+
const hermesDir = join32(repoPath, "agents", "hermes");
|
|
19236
19677
|
if (!existsSync22(hermesDir)) return { status: "skipped", detail: "no agents/hermes directory" };
|
|
19237
19678
|
if (!isPlane) return { status: "skipped", detail: "role.yaml plane.identifier is a Plane field" };
|
|
19238
19679
|
const files = [];
|
|
@@ -19240,16 +19681,16 @@ async function renameProject(options) {
|
|
|
19240
19681
|
let settled = 0;
|
|
19241
19682
|
let entries;
|
|
19242
19683
|
try {
|
|
19243
|
-
entries =
|
|
19684
|
+
entries = readdirSync12(hermesDir, { withFileTypes: true });
|
|
19244
19685
|
} catch (error) {
|
|
19245
19686
|
return { status: "failed", detail: `${hermesDir}: ${message(error)}` };
|
|
19246
19687
|
}
|
|
19247
19688
|
for (const entry of entries) {
|
|
19248
19689
|
if (!entry.isDirectory()) continue;
|
|
19249
|
-
const path =
|
|
19690
|
+
const path = join32(hermesDir, entry.name, "role.yaml");
|
|
19250
19691
|
if (!existsSync22(path)) continue;
|
|
19251
19692
|
try {
|
|
19252
|
-
const original =
|
|
19693
|
+
const original = readFileSync28(path, "utf8");
|
|
19253
19694
|
const document = YAML8.parseDocument(original);
|
|
19254
19695
|
if (document.errors.length) throw new Error("YAML is invalid");
|
|
19255
19696
|
const node = document.getIn(["plane", "identifier"], true);
|
|
@@ -19307,7 +19748,7 @@ async function renameProject(options) {
|
|
|
19307
19748
|
}
|
|
19308
19749
|
};
|
|
19309
19750
|
const agentsMdStep = async (doApply) => {
|
|
19310
|
-
const link =
|
|
19751
|
+
const link = join32(repoPath, "AGENTS.md");
|
|
19311
19752
|
if (!existsSync22(link)) return { status: "skipped", detail: "no AGENTS.md" };
|
|
19312
19753
|
try {
|
|
19313
19754
|
const path = realpathSync11(link);
|
|
@@ -19315,7 +19756,7 @@ async function renameProject(options) {
|
|
|
19315
19756
|
if (inside.startsWith("..") || isAbsolute11(inside)) {
|
|
19316
19757
|
return { status: "skipped", detail: `AGENTS.md resolves outside the repo (${path}); left untouched` };
|
|
19317
19758
|
}
|
|
19318
|
-
const original =
|
|
19759
|
+
const original = readFileSync28(path, "utf8");
|
|
19319
19760
|
const lines = original.split("\n");
|
|
19320
19761
|
let boardLines = 0;
|
|
19321
19762
|
let replaced = 0;
|
|
@@ -19345,8 +19786,8 @@ async function renameProject(options) {
|
|
|
19345
19786
|
}
|
|
19346
19787
|
};
|
|
19347
19788
|
const hintsStep = async (doApply) => {
|
|
19348
|
-
const configDir = env2.PILOT_CONFIG_DIR?.trim() ||
|
|
19349
|
-
const path =
|
|
19789
|
+
const configDir = env2.PILOT_CONFIG_DIR?.trim() || join32(env2.XDG_CONFIG_HOME?.trim() || join32(home, ".config"), "pilot");
|
|
19790
|
+
const path = join32(configDir, "harvest", "project-hints.json");
|
|
19350
19791
|
if (!existsSync22(path)) return { status: "skipped", detail: `no harvest hints at ${path}` };
|
|
19351
19792
|
try {
|
|
19352
19793
|
const hints = readJsonObject(path);
|
|
@@ -19380,7 +19821,7 @@ async function renameProject(options) {
|
|
|
19380
19821
|
if (oldInput === newId && steps.every((step) => step.status === "already" || step.status === "skipped")) {
|
|
19381
19822
|
return { status: "already", detail: "nothing changed; projection not re-synced" };
|
|
19382
19823
|
}
|
|
19383
|
-
const cwd =
|
|
19824
|
+
const cwd = join32(home, "code", "33GOD", "candystore");
|
|
19384
19825
|
try {
|
|
19385
19826
|
if (!statSync6(cwd).isDirectory()) throw new Error("not a directory");
|
|
19386
19827
|
} catch {
|
|
@@ -19475,7 +19916,7 @@ init_boardQuery();
|
|
|
19475
19916
|
// src/describe/activity.ts
|
|
19476
19917
|
import { spawn as spawn3, spawnSync as spawnSync16 } from "node:child_process";
|
|
19477
19918
|
import { statSync as statSync7 } from "node:fs";
|
|
19478
|
-
import { join as
|
|
19919
|
+
import { join as join33 } from "node:path";
|
|
19479
19920
|
var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
|
|
19480
19921
|
var MAX_DIRTY_STATS = 500;
|
|
19481
19922
|
var GIT_TIMEOUT_MS = 5e3;
|
|
@@ -19640,7 +20081,7 @@ function uncommittedSource(repo, paths) {
|
|
|
19640
20081
|
let newest = 0;
|
|
19641
20082
|
for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
|
|
19642
20083
|
try {
|
|
19643
|
-
const mtime = Math.floor(statSync7(
|
|
20084
|
+
const mtime = Math.floor(statSync7(join33(repo, path)).mtimeMs / 1e3);
|
|
19644
20085
|
if (mtime > newest) newest = mtime;
|
|
19645
20086
|
} catch {
|
|
19646
20087
|
}
|
|
@@ -19838,8 +20279,8 @@ function openUrl(url, env2 = process.env, platform = process.platform) {
|
|
|
19838
20279
|
}
|
|
19839
20280
|
|
|
19840
20281
|
// src/describe/index.ts
|
|
19841
|
-
import { existsSync as existsSync23, readFileSync as
|
|
19842
|
-
import { join as
|
|
20282
|
+
import { existsSync as existsSync23, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync8 } from "node:fs";
|
|
20283
|
+
import { join as join34, resolve as resolve23 } from "node:path";
|
|
19843
20284
|
init_project();
|
|
19844
20285
|
init_config();
|
|
19845
20286
|
init_state();
|
|
@@ -19895,7 +20336,7 @@ var CONFIG_FILES = [
|
|
|
19895
20336
|
];
|
|
19896
20337
|
function readJson(path) {
|
|
19897
20338
|
try {
|
|
19898
|
-
const parsed = JSON.parse(
|
|
20339
|
+
const parsed = JSON.parse(readFileSync29(path, "utf8"));
|
|
19899
20340
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
19900
20341
|
} catch {
|
|
19901
20342
|
return void 0;
|
|
@@ -19917,23 +20358,23 @@ function describeType(repo) {
|
|
|
19917
20358
|
const evidence = [];
|
|
19918
20359
|
const note = (signal, file) => evidence.push(`${signal} (${file})`);
|
|
19919
20360
|
for (const marker of LANGUAGE_MARKERS) {
|
|
19920
|
-
if (!existsSync23(
|
|
20361
|
+
if (!existsSync23(join34(repo, marker.file))) continue;
|
|
19921
20362
|
if (!languages.includes(marker.language)) {
|
|
19922
20363
|
languages.push(marker.language);
|
|
19923
20364
|
note(marker.language, marker.file);
|
|
19924
20365
|
}
|
|
19925
20366
|
}
|
|
19926
20367
|
try {
|
|
19927
|
-
const dotnet =
|
|
20368
|
+
const dotnet = readdirSync13(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
|
|
19928
20369
|
if (dotnet && !languages.includes("dotnet")) {
|
|
19929
20370
|
languages.push("dotnet");
|
|
19930
20371
|
note("dotnet", dotnet);
|
|
19931
20372
|
}
|
|
19932
20373
|
} catch {
|
|
19933
20374
|
}
|
|
19934
|
-
const pkg = readJson(
|
|
20375
|
+
const pkg = readJson(join34(repo, "package.json"));
|
|
19935
20376
|
if (pkg) {
|
|
19936
|
-
if (existsSync23(
|
|
20377
|
+
if (existsSync23(join34(repo, "tsconfig.json"))) {
|
|
19937
20378
|
const index = languages.indexOf("javascript");
|
|
19938
20379
|
if (index >= 0) languages.splice(index, 1);
|
|
19939
20380
|
if (!languages.includes("typescript")) {
|
|
@@ -19967,14 +20408,14 @@ function describeType(repo) {
|
|
|
19967
20408
|
["hermes-fleet-host", "agents/hermes"]
|
|
19968
20409
|
];
|
|
19969
20410
|
for (const [role, marker] of roleMarkers) {
|
|
19970
|
-
if (!existsSync23(
|
|
20411
|
+
if (!existsSync23(join34(repo, marker))) continue;
|
|
19971
20412
|
roles.push(role);
|
|
19972
20413
|
note(role, marker);
|
|
19973
20414
|
}
|
|
19974
20415
|
return { primaryLanguage: languages[0], languages, roles, evidence };
|
|
19975
20416
|
}
|
|
19976
20417
|
function describeIdentity(repo, registryPath) {
|
|
19977
|
-
const manifestPath =
|
|
20418
|
+
const manifestPath = join34(repo, ".project.json");
|
|
19978
20419
|
const manifest = readJson(manifestPath);
|
|
19979
20420
|
const drift = [];
|
|
19980
20421
|
let record3;
|
|
@@ -20067,12 +20508,12 @@ function describeSubsystems(repo, findings) {
|
|
|
20067
20508
|
const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
|
|
20068
20509
|
const evidence = metadata.id === "notebook" ? (() => {
|
|
20069
20510
|
try {
|
|
20070
|
-
const manifest = JSON.parse(
|
|
20511
|
+
const manifest = JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8"));
|
|
20071
20512
|
return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
|
|
20072
20513
|
} catch {
|
|
20073
20514
|
return [];
|
|
20074
20515
|
}
|
|
20075
|
-
})() : markers.filter((marker) => existsSync23(
|
|
20516
|
+
})() : markers.filter((marker) => existsSync23(join34(repo, marker)));
|
|
20076
20517
|
const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
|
|
20077
20518
|
id: finding2.id,
|
|
20078
20519
|
title: finding2.title,
|
|
@@ -20091,7 +20532,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
20091
20532
|
try {
|
|
20092
20533
|
const registry = loadProjectRegistry(registryPath);
|
|
20093
20534
|
const project = Object.values(registry.projects).find((entry) => resolve23(entry.repo_path) === resolve23(repo));
|
|
20094
|
-
const manifest = existsSync23(
|
|
20535
|
+
const manifest = existsSync23(join34(repo, ".project.json")) ? JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8")) : void 0;
|
|
20095
20536
|
const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
|
|
20096
20537
|
if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
|
|
20097
20538
|
const config = loadEffectiveNotebookConfig(repo, registryPath);
|
|
@@ -20109,7 +20550,7 @@ function describeNotebook(repo, registryPath) {
|
|
|
20109
20550
|
}
|
|
20110
20551
|
}
|
|
20111
20552
|
function describeConfigFiles(repo) {
|
|
20112
|
-
return CONFIG_FILES.filter((spec) => existsSync23(
|
|
20553
|
+
return CONFIG_FILES.filter((spec) => existsSync23(join34(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
|
|
20113
20554
|
}
|
|
20114
20555
|
function describeNextSteps(description, findings) {
|
|
20115
20556
|
const steps = [];
|
|
@@ -20533,7 +20974,7 @@ init_style();
|
|
|
20533
20974
|
// src/notebook/cli.ts
|
|
20534
20975
|
init_project();
|
|
20535
20976
|
init_capture();
|
|
20536
|
-
import { closeSync as
|
|
20977
|
+
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
20978
|
import { resolve as resolve24 } from "node:path";
|
|
20538
20979
|
import { createInterface } from "node:readline/promises";
|
|
20539
20980
|
|
|
@@ -20676,12 +21117,12 @@ async function execute(input) {
|
|
|
20676
21117
|
function readBoundedRegularFile(path, maxBytes) {
|
|
20677
21118
|
const absolute = resolve24(path);
|
|
20678
21119
|
if (!existsSync24(absolute)) throw new NotebookError("INVALID_INPUT", `File not found: ${absolute}`);
|
|
20679
|
-
const before =
|
|
21120
|
+
const before = lstatSync19(absolute);
|
|
20680
21121
|
if (!before.isFile() || before.isSymbolicLink()) throw new NotebookError("INVALID_INPUT", `File must be a regular non-symlink: ${absolute}`);
|
|
20681
21122
|
if (before.size > maxBytes) throw new NotebookError("INVALID_INPUT", `File exceeds the configured ceiling: ${absolute}`);
|
|
20682
|
-
const fd =
|
|
21123
|
+
const fd = openSync9(absolute, constants7.O_RDONLY | (constants7.O_NOFOLLOW ?? 0));
|
|
20683
21124
|
try {
|
|
20684
|
-
const opened =
|
|
21125
|
+
const opened = fstatSync5(fd);
|
|
20685
21126
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", `File changed while opening: ${absolute}`);
|
|
20686
21127
|
const chunks = [];
|
|
20687
21128
|
let total = 0;
|
|
@@ -20699,7 +21140,7 @@ function readBoundedRegularFile(path, maxBytes) {
|
|
|
20699
21140
|
throw new NotebookError("INVALID_INPUT", `File must be valid UTF-8: ${absolute}`);
|
|
20700
21141
|
}
|
|
20701
21142
|
} finally {
|
|
20702
|
-
|
|
21143
|
+
closeSync9(fd);
|
|
20703
21144
|
}
|
|
20704
21145
|
}
|
|
20705
21146
|
function textInput(options, maxBytes) {
|
|
@@ -20951,14 +21392,14 @@ async function loadScreen(relativeToDist, base) {
|
|
|
20951
21392
|
init_boardQuery();
|
|
20952
21393
|
init_style();
|
|
20953
21394
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
20954
|
-
import { mkdirSync as
|
|
21395
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync31, realpathSync as realpathSync12, renameSync as renameSync10, writeFileSync as writeFileSync16 } from "node:fs";
|
|
20955
21396
|
import { homedir as homedir15 } from "node:os";
|
|
20956
|
-
import { dirname as dirname22, join as
|
|
21397
|
+
import { dirname as dirname22, join as join36 } from "node:path";
|
|
20957
21398
|
|
|
20958
21399
|
// src/crank/engine.ts
|
|
20959
|
-
import { closeSync as
|
|
21400
|
+
import { closeSync as closeSync10, constants as constants8, fstatSync as fstatSync6, openSync as openSync10, readFileSync as readFileSync30, readSync as readSync5 } from "node:fs";
|
|
20960
21401
|
import { homedir as homedir14 } from "node:os";
|
|
20961
|
-
import { join as
|
|
21402
|
+
import { join as join35 } from "node:path";
|
|
20962
21403
|
var ENGINE_DEFAULT_JOB_ID = "777c511a009c";
|
|
20963
21404
|
var WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
20964
21405
|
var TICK_MS = 5 * 60 * 1e3;
|
|
@@ -21058,9 +21499,9 @@ function parseEngineStatus(auditText, input) {
|
|
|
21058
21499
|
}
|
|
21059
21500
|
}
|
|
21060
21501
|
function readTail(path, maxBytes) {
|
|
21061
|
-
const fd =
|
|
21502
|
+
const fd = openSync10(path, constants8.O_RDONLY | constants8.O_NONBLOCK);
|
|
21062
21503
|
try {
|
|
21063
|
-
const stat =
|
|
21504
|
+
const stat = fstatSync6(fd);
|
|
21064
21505
|
if (!stat.isFile()) throw new Error(`${path} is not a regular file`);
|
|
21065
21506
|
const size = stat.size;
|
|
21066
21507
|
const length = Math.min(size, maxBytes);
|
|
@@ -21076,20 +21517,20 @@ function readTail(path, maxBytes) {
|
|
|
21076
21517
|
const newline = text2.indexOf("\n");
|
|
21077
21518
|
return newline === -1 ? "" : text2.slice(newline + 1);
|
|
21078
21519
|
} finally {
|
|
21079
|
-
|
|
21520
|
+
closeSync10(fd);
|
|
21080
21521
|
}
|
|
21081
21522
|
}
|
|
21082
21523
|
function engineSources(env2 = process.env) {
|
|
21083
|
-
const cronDir =
|
|
21524
|
+
const cronDir = join35(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
|
|
21084
21525
|
return {
|
|
21085
|
-
auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() ||
|
|
21086
|
-
jobsPath:
|
|
21526
|
+
auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join35(cronDir, "usage_audit.jsonl"),
|
|
21527
|
+
jobsPath: join35(cronDir, "jobs.json"),
|
|
21087
21528
|
jobId: env2.PJ_CRANK_JOB_ID?.trim() || ENGINE_DEFAULT_JOB_ID
|
|
21088
21529
|
};
|
|
21089
21530
|
}
|
|
21090
21531
|
function readCap(jobsPath, jobId) {
|
|
21091
21532
|
try {
|
|
21092
|
-
const parsed = JSON.parse(
|
|
21533
|
+
const parsed = JSON.parse(readFileSync30(jobsPath, "utf8"));
|
|
21093
21534
|
const jobs = Array.isArray(parsed.jobs) ? parsed.jobs : [];
|
|
21094
21535
|
const job = jobs.find((j) => typeof j === "object" && j !== null && String(j.id ?? "") === jobId);
|
|
21095
21536
|
const economics = job?.economics;
|
|
@@ -21475,11 +21916,11 @@ function withTabOwners(tabs, panes, identify, claimed = []) {
|
|
|
21475
21916
|
}
|
|
21476
21917
|
var PANE_CWD_TTL_SECONDS = 6 * 3600;
|
|
21477
21918
|
function paneCwdCachePath(env2 = process.env) {
|
|
21478
|
-
return env2.ZCRANK_CWD_CACHE?.trim() ||
|
|
21919
|
+
return env2.ZCRANK_CWD_CACHE?.trim() || join36(homedir15(), ".local/state/zcrank/pane-cwds.json");
|
|
21479
21920
|
}
|
|
21480
21921
|
function loadCwdCache(path) {
|
|
21481
21922
|
try {
|
|
21482
|
-
const parsed = JSON.parse(
|
|
21923
|
+
const parsed = JSON.parse(readFileSync31(path, "utf8"));
|
|
21483
21924
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
21484
21925
|
} catch {
|
|
21485
21926
|
return {};
|
|
@@ -21487,9 +21928,9 @@ function loadCwdCache(path) {
|
|
|
21487
21928
|
}
|
|
21488
21929
|
function saveCwdCache(path, cache) {
|
|
21489
21930
|
try {
|
|
21490
|
-
|
|
21931
|
+
mkdirSync13(dirname22(path), { recursive: true });
|
|
21491
21932
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
21492
|
-
|
|
21933
|
+
writeFileSync16(tmp, JSON.stringify(cache));
|
|
21493
21934
|
renameSync10(tmp, path);
|
|
21494
21935
|
} catch {
|
|
21495
21936
|
}
|
|
@@ -21642,7 +22083,7 @@ function resolveByName(project, live, storedTabs) {
|
|
|
21642
22083
|
function projectNameFor(handle, registryName) {
|
|
21643
22084
|
if (registryName && registryName.trim()) return registryName.trim();
|
|
21644
22085
|
try {
|
|
21645
|
-
const manifest = JSON.parse(
|
|
22086
|
+
const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
|
|
21646
22087
|
const name = manifest.project_name;
|
|
21647
22088
|
if (typeof name === "string" && name.trim()) return name.trim();
|
|
21648
22089
|
} catch {
|
|
@@ -21653,7 +22094,7 @@ function projectAgentsFor(handle, registryAgents) {
|
|
|
21653
22094
|
const fromRecord = Object.entries(registryAgents ?? {}).map(([agentHandle, value]) => ({ handle: agentHandle, role: String(value?.role ?? "") }));
|
|
21654
22095
|
if (fromRecord.length) return fromRecord;
|
|
21655
22096
|
try {
|
|
21656
|
-
const manifest = JSON.parse(
|
|
22097
|
+
const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
|
|
21657
22098
|
const agents = manifest.agents;
|
|
21658
22099
|
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
21659
22100
|
return Object.entries(agents).map(([agentHandle, value]) => ({ handle: agentHandle, role: typeof value?.role === "string" ? value.role : "" }));
|
|
@@ -21971,9 +22412,9 @@ function createCrankController(deps) {
|
|
|
21971
22412
|
}
|
|
21972
22413
|
|
|
21973
22414
|
// src/crank/panes.ts
|
|
21974
|
-
import { readFileSync as
|
|
22415
|
+
import { readFileSync as readFileSync32, readdirSync as readdirSync14, readlinkSync as readlinkSync9 } from "node:fs";
|
|
21975
22416
|
import { homedir as homedir16 } from "node:os";
|
|
21976
|
-
import { basename as
|
|
22417
|
+
import { basename as basename12, join as join37 } from "node:path";
|
|
21977
22418
|
var AGENT_CLIS = [
|
|
21978
22419
|
"claude",
|
|
21979
22420
|
"codex",
|
|
@@ -22055,7 +22496,7 @@ function parseProcStat(text2) {
|
|
|
22055
22496
|
}
|
|
22056
22497
|
function readProcStat(pid) {
|
|
22057
22498
|
try {
|
|
22058
|
-
return parseProcStat(
|
|
22499
|
+
return parseProcStat(readFileSync32(`/proc/${pid}/stat`, "latin1"));
|
|
22059
22500
|
} catch {
|
|
22060
22501
|
return null;
|
|
22061
22502
|
}
|
|
@@ -22063,7 +22504,7 @@ function readProcStat(pid) {
|
|
|
22063
22504
|
function readTag(pid) {
|
|
22064
22505
|
let raw;
|
|
22065
22506
|
try {
|
|
22066
|
-
raw =
|
|
22507
|
+
raw = readFileSync32(`/proc/${pid}/environ`);
|
|
22067
22508
|
} catch {
|
|
22068
22509
|
return null;
|
|
22069
22510
|
}
|
|
@@ -22089,8 +22530,8 @@ function readTag(pid) {
|
|
|
22089
22530
|
}
|
|
22090
22531
|
function readArgv(pid) {
|
|
22091
22532
|
try {
|
|
22092
|
-
const parts =
|
|
22093
|
-
return { argv: parts.join(" "), exe: parts[0] ?
|
|
22533
|
+
const parts = readFileSync32(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
|
|
22534
|
+
return { argv: parts.join(" "), exe: parts[0] ? basename12(parts[0]) : "" };
|
|
22094
22535
|
} catch {
|
|
22095
22536
|
return { argv: "", exe: "" };
|
|
22096
22537
|
}
|
|
@@ -22130,7 +22571,7 @@ var ProcScanner = class {
|
|
|
22130
22571
|
pids = [...this.enumerate()];
|
|
22131
22572
|
} else {
|
|
22132
22573
|
try {
|
|
22133
|
-
pids =
|
|
22574
|
+
pids = readdirSync14("/proc").filter((name) => /^\d+$/.test(name)).map(Number);
|
|
22134
22575
|
} catch {
|
|
22135
22576
|
return [];
|
|
22136
22577
|
}
|
|
@@ -22166,13 +22607,13 @@ var ProcScanner = class {
|
|
|
22166
22607
|
var PANE_STATUSES = ["busy", "idle", "shell"];
|
|
22167
22608
|
function claudeSessionsDir(env2 = process.env) {
|
|
22168
22609
|
const override = env2.PJ_CLAUDE_SESSIONS_DIR?.trim();
|
|
22169
|
-
return override ||
|
|
22610
|
+
return override || join37(homedir16(), ".claude", "sessions");
|
|
22170
22611
|
}
|
|
22171
22612
|
function readClaudeSession(pid, stat, dir = claudeSessionsDir()) {
|
|
22172
22613
|
const none = (reason) => ({ kind: null, sessionId: null, reason, status: "unknown", statusUpdatedAt: null });
|
|
22173
22614
|
let raw;
|
|
22174
22615
|
try {
|
|
22175
|
-
raw =
|
|
22616
|
+
raw = readFileSync32(join37(dir, `${pid}.json`), "utf8");
|
|
22176
22617
|
} catch {
|
|
22177
22618
|
return none("no-session-file");
|
|
22178
22619
|
}
|
|
@@ -22633,7 +23074,7 @@ async function promptForRuleIds(rules) {
|
|
|
22633
23074
|
function readJson2(path) {
|
|
22634
23075
|
if (!existsSync26(path)) return void 0;
|
|
22635
23076
|
try {
|
|
22636
|
-
const parsed = JSON.parse(
|
|
23077
|
+
const parsed = JSON.parse(readFileSync33(path, "utf8"));
|
|
22637
23078
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
22638
23079
|
} catch {
|
|
22639
23080
|
return void 0;
|
|
@@ -22650,9 +23091,9 @@ function packageNameToProjectName(value) {
|
|
|
22650
23091
|
return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()).trim();
|
|
22651
23092
|
}
|
|
22652
23093
|
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(
|
|
23094
|
+
const manifest = readJson2(join38(targetDir, ".project.json"));
|
|
23095
|
+
const pkg = readJson2(join38(targetDir, "package.json"));
|
|
23096
|
+
const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename13(targetDir)) || "Project";
|
|
22656
23097
|
const ticketProvider = manifest?.ticket_provider && typeof manifest.ticket_provider === "object" ? manifest.ticket_provider : {};
|
|
22657
23098
|
return {
|
|
22658
23099
|
name,
|
|
@@ -22726,7 +23167,7 @@ function actionNeedsRun(plan, kind, syncMode) {
|
|
|
22726
23167
|
if (!action || action.kind !== "project.write-manifest") return false;
|
|
22727
23168
|
const next = `${JSON.stringify(action.manifest, null, 2)}
|
|
22728
23169
|
`;
|
|
22729
|
-
return !existsSync26(action.path) ||
|
|
23170
|
+
return !existsSync26(action.path) || readFileSync33(action.path, "utf8") !== next;
|
|
22730
23171
|
}
|
|
22731
23172
|
if (kind === "copier.copy.commonproject") return true;
|
|
22732
23173
|
if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
|
|
@@ -22783,11 +23224,11 @@ async function resolveProjectInitTarget(name, options) {
|
|
|
22783
23224
|
if (!targetDir && cwdGitRoot) {
|
|
22784
23225
|
targetDir = cwdGitRoot;
|
|
22785
23226
|
}
|
|
22786
|
-
if (!targetDir && !name && existsSync26(
|
|
23227
|
+
if (!targetDir && !name && existsSync26(join38(cwd, ".project.json"))) {
|
|
22787
23228
|
targetDir = cwd;
|
|
22788
23229
|
}
|
|
22789
23230
|
if (!targetDir && interactive) {
|
|
22790
|
-
const promptedName = await promptTextValue("Project name", packageNameToProjectName(
|
|
23231
|
+
const promptedName = await promptTextValue("Project name", packageNameToProjectName(basename13(cwd)));
|
|
22791
23232
|
targetDir = resolve25(await promptTextValue("Project directory", projectTargetDirUnder(promptedName, cwd)));
|
|
22792
23233
|
name = promptedName;
|
|
22793
23234
|
}
|
|
@@ -22797,14 +23238,14 @@ async function resolveProjectInitTarget(name, options) {
|
|
|
22797
23238
|
const targetExists = existsSync26(targetDir);
|
|
22798
23239
|
if (targetExists && !statSync9(targetDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetDir}`);
|
|
22799
23240
|
const targetGitRoot = targetExists ? findGitRoot(targetDir) : void 0;
|
|
22800
|
-
const alreadyScaffolded = targetExists && (existsSync26(
|
|
23241
|
+
const alreadyScaffolded = targetExists && (existsSync26(join38(targetDir, ".project.json")) || existsSync26(join38(targetDir, ".copier-answers.yml")));
|
|
22801
23242
|
const syncMode = Boolean(
|
|
22802
23243
|
targetGitRoot && resolve25(targetGitRoot) === resolve25(targetDir) || alreadyScaffolded
|
|
22803
23244
|
);
|
|
22804
|
-
if (targetExists && !syncMode && !options.force &&
|
|
23245
|
+
if (targetExists && !syncMode && !options.force && readdirSync15(targetDir).length > 0) {
|
|
22805
23246
|
throw new Error(`Target already exists and is not empty: ${targetDir} (pass --force to render over it)`);
|
|
22806
23247
|
}
|
|
22807
|
-
const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(
|
|
23248
|
+
const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(basename13(targetDir)) ?? "Project", description: "" };
|
|
22808
23249
|
if (!name && interactive && !syncMode) {
|
|
22809
23250
|
name = await promptTextValue("Project name", defaults.name);
|
|
22810
23251
|
}
|
|
@@ -23319,7 +23760,7 @@ program.command("reindex").argument("[project-id]", "Canonical project ID (defau
|
|
|
23319
23760
|
else if (options.all) result2 = registryRequest(location, "POST", "/v1/reindex", {});
|
|
23320
23761
|
else {
|
|
23321
23762
|
const repo = projectId ? getProject(loadProjectRegistry(location), projectId).repo_path : resolveProject(process.cwd()).root;
|
|
23322
|
-
result2 = registryRequest(location, "POST", "/v1/index", { manifest_path:
|
|
23763
|
+
result2 = registryRequest(location, "POST", "/v1/index", { manifest_path: join38(repo, ".project.json") });
|
|
23323
23764
|
}
|
|
23324
23765
|
const statuses = result2.__registry_status;
|
|
23325
23766
|
const stale = Object.entries(statuses ?? {}).filter(([, item]) => item.status !== "ok");
|