@delorenj/pjangler 1.5.11 → 1.5.13
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/.mise/scripts/versioning.sh +26 -5
- package/README.md +25 -0
- package/dist/index.js +169 -36
- package/dist/mcp-server.js +153 -29
- package/dist/notebook/journal-service-main.js +4 -1
- package/dist/project-registry-service.js +1 -1
- package/package.json +1 -1
- package/templates/commonproject/copier.yml +102 -1
- package/templates/commonproject/template/.github/workflows/{% if is_obsidian_plugin %}lint.yml{% endif %} +29 -0
- package/templates/commonproject/template/.github/workflows/{% if is_obsidian_plugin %}release.yml{% endif %} +62 -0
- package/templates/commonproject/template/.mise/{% if is_obsidian_plugin %}tasks{% endif %}/plugin/link +27 -0
- package/templates/commonproject/template/AGENTS.md.jinja +67 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}.editorconfig{% endif %} +15 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}.npmrc{% endif %} +1 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}esbuild.config.mjs{% endif %} +49 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}eslint.config.mts{% endif %} +38 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}manifest.json{% endif %}.jinja +9 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}package-lock.json{% endif %}.jinja +5053 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}package.json{% endif %}.jinja +28 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}src{% endif %}/main.ts.jinja +105 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}src{% endif %}/settings.ts.jinja +38 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}styles.css{% endif %} +8 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}tsconfig.json{% endif %} +19 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}version-bump.mjs{% endif %} +17 -0
- package/templates/commonproject/template/{% if is_obsidian_plugin %}versions.json{% endif %} +3 -0
package/dist/mcp-server.js
CHANGED
|
@@ -828,6 +828,38 @@ import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
|
828
828
|
import { basename as basename6, delimiter as delimiter2, dirname as dirname10, isAbsolute as isAbsolute5, join as join11, relative as relative7, resolve as resolve9, sep as sep2, win32 } from "node:path";
|
|
829
829
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
830
830
|
import YAML4 from "yaml";
|
|
831
|
+
function normalizeProjectType(value, label = "Project type") {
|
|
832
|
+
const text = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
833
|
+
if (PROJECT_TYPES.includes(text)) return text;
|
|
834
|
+
throw new Error(`${label} ${JSON.stringify(value)} is not one of: ${PROJECT_TYPES.join(", ")}`);
|
|
835
|
+
}
|
|
836
|
+
function looksLikeObsidianPlugin(dir) {
|
|
837
|
+
try {
|
|
838
|
+
const manifest = JSON.parse(readFileSync8(join11(dir, "manifest.json"), "utf8"));
|
|
839
|
+
return isRecord2(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
840
|
+
} catch {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
|
|
845
|
+
if (projectType === "base") return;
|
|
846
|
+
const copierYml = join11(pjanglerRoot, "templates", "commonproject", "copier.yml");
|
|
847
|
+
let choices;
|
|
848
|
+
try {
|
|
849
|
+
const parsed = YAML4.parse(readFileSync8(copierYml, "utf8"));
|
|
850
|
+
choices = isRecord2(parsed) && isRecord2(parsed.project_type) ? parsed.project_type.choices : void 0;
|
|
851
|
+
} catch {
|
|
852
|
+
choices = void 0;
|
|
853
|
+
}
|
|
854
|
+
const offered = isRecord2(choices) ? Object.values(choices) : Array.isArray(choices) ? choices : [];
|
|
855
|
+
if (!offered.includes(projectType)) {
|
|
856
|
+
throw new Error(`The CommonProject template at ${dirname10(copierYml)} cannot render project type "${projectType}" (its copier.yml offers no such project_type); update pjangler or its templates/commonproject pin`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function operatorDisplayName(cwd) {
|
|
860
|
+
const result2 = spawnSync4("git", ["config", "--get", "user.name"], { cwd: existsSync7(cwd) ? cwd : void 0, encoding: "utf8", timeout: 5e3 });
|
|
861
|
+
return result2.status === 0 ? result2.stdout.trim() : "";
|
|
862
|
+
}
|
|
831
863
|
function providerAssignsIdentifiers(type) {
|
|
832
864
|
return IDENTIFIER_ASSIGNING_PROVIDERS.includes((type ?? "").trim());
|
|
833
865
|
}
|
|
@@ -1431,6 +1463,26 @@ function planProjectInit(input) {
|
|
|
1431
1463
|
const overwrite = input.overwrite ?? input.force ?? false;
|
|
1432
1464
|
const agents = createSafeRecord(Object.entries(existing?.agents ?? {}));
|
|
1433
1465
|
const scaffold = input.scaffold ?? true;
|
|
1466
|
+
const recordedTypeValue = existing?.template?.commonproject?.project_type;
|
|
1467
|
+
const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
|
|
1468
|
+
const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
|
|
1469
|
+
const freshRender = scaffold && localManifest === void 0 && !existsSync7(join11(targetDir, ".copier-answers.yml"));
|
|
1470
|
+
if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
|
|
1471
|
+
const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
|
|
1472
|
+
if (!adoptingPlugin) {
|
|
1473
|
+
throw new Error(scaffold || recordedType !== void 0 ? `Project type "${requestedType}" conflicts with ${slug}'s recorded type "${recordedType ?? "base"}"; a project's type is fixed when it is created` : `${targetDir} is adopted as-is, so "${requestedType}" would render nothing, and the repo is not one already (no Obsidian manifest.json with id and minAppVersion)`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const projectType = requestedType ?? recordedType ?? "base";
|
|
1477
|
+
const typeLanguages = PROJECT_TYPE_LANGUAGES[projectType];
|
|
1478
|
+
if (input.primaryLanguage !== void 0 && typeLanguages && !typeLanguages.includes(input.primaryLanguage)) {
|
|
1479
|
+
throw new Error(`A ${projectType} project is written in ${typeLanguages.join(" or ")}, not ${input.primaryLanguage}`);
|
|
1480
|
+
}
|
|
1481
|
+
const recordedLanguageValue = (localManifest ? localManifest.template : indexed?.template)?.commonproject?.primary_language;
|
|
1482
|
+
const recordedLanguage = typeof recordedLanguageValue === "string" && recordedLanguageValue ? recordedLanguageValue : void 0;
|
|
1483
|
+
const newlyTyped = requestedType !== void 0 && requestedType !== recordedType;
|
|
1484
|
+
const recordedLanguageFits = !typeLanguages || recordedLanguage !== void 0 && typeLanguages.includes(recordedLanguage);
|
|
1485
|
+
const primaryLanguage = input.primaryLanguage ?? (newlyTyped && !recordedLanguageFits ? typeLanguages?.[0] : void 0) ?? recordedLanguage ?? typeLanguages?.[0] ?? "python";
|
|
1434
1486
|
const candidateProject = {
|
|
1435
1487
|
...existing ?? {},
|
|
1436
1488
|
name: input.name,
|
|
@@ -1452,7 +1504,10 @@ function planProjectInit(input) {
|
|
|
1452
1504
|
commonproject: {
|
|
1453
1505
|
...existing?.template?.commonproject,
|
|
1454
1506
|
enabled: true,
|
|
1455
|
-
primary_language:
|
|
1507
|
+
primary_language: primaryLanguage,
|
|
1508
|
+
// Written only for a non-base type (or one already recorded), so a
|
|
1509
|
+
// base record never gains a key and stays equivalent to its last run.
|
|
1510
|
+
...projectType !== "base" || recordedType !== void 0 ? { project_type: projectType } : {}
|
|
1456
1511
|
}
|
|
1457
1512
|
},
|
|
1458
1513
|
ticket_provider: { ...existing?.ticket_provider, ...buildTicketProviderBlock({
|
|
@@ -1488,6 +1543,7 @@ function planProjectInit(input) {
|
|
|
1488
1543
|
{ kind: "registry.upsert", registryPath, slug, project }
|
|
1489
1544
|
];
|
|
1490
1545
|
if (scaffold) {
|
|
1546
|
+
assertTemplateOffersProjectType(pjanglerRoot, projectType);
|
|
1491
1547
|
actions.push(buildCommonProjectCopierAction({
|
|
1492
1548
|
pjanglerRoot,
|
|
1493
1549
|
targetDir,
|
|
@@ -1501,6 +1557,8 @@ function planProjectInit(input) {
|
|
|
1501
1557
|
boardId: project.ticket_provider.board_id ?? "",
|
|
1502
1558
|
projectIdentifier: identifier,
|
|
1503
1559
|
primaryLanguage: project.template.commonproject.primary_language,
|
|
1560
|
+
projectType,
|
|
1561
|
+
...projectType === "obsidian-plugin" ? { pluginAuthor: operatorDisplayName(input.cwd ?? process.cwd()) } : {},
|
|
1504
1562
|
agentHooksLayer: resolveAgentHooksLayer2(input.agentHooksLayer),
|
|
1505
1563
|
overwrite
|
|
1506
1564
|
}));
|
|
@@ -1639,6 +1697,13 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1639
1697
|
errors.push(`copier exited with status ${result2.status ?? "unknown"}`);
|
|
1640
1698
|
break;
|
|
1641
1699
|
}
|
|
1700
|
+
const projectType = action.data.project_type ?? "base";
|
|
1701
|
+
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync7(join11(action.targetDir, rel)));
|
|
1702
|
+
if (missing.length) {
|
|
1703
|
+
errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
|
|
1642
1707
|
} else if (action.kind === "project.write-manifest") {
|
|
1643
1708
|
if (action.expectedContent !== void 0 && (!existsSync7(action.path) || readFileSync8(action.path, "utf8") !== action.expectedContent)) {
|
|
1644
1709
|
errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
|
|
@@ -1771,6 +1836,8 @@ function buildCommonProjectCopierAction(input) {
|
|
|
1771
1836
|
board_id: input.boardId ?? input.planeProjectId ?? "",
|
|
1772
1837
|
project_identifier: input.projectIdentifier,
|
|
1773
1838
|
primary_language: input.primaryLanguage,
|
|
1839
|
+
project_type: input.projectType ?? "base",
|
|
1840
|
+
...input.pluginAuthor !== void 0 ? { plugin_author: input.pluginAuthor } : {},
|
|
1774
1841
|
agent_hooks_layer: input.agentHooksLayer ?? true ? "true" : "false"
|
|
1775
1842
|
};
|
|
1776
1843
|
const command = ["copier", "copy", "--trust", "--vcs-ref=HEAD", templateDir, input.targetDir, "--defaults"];
|
|
@@ -1871,7 +1938,7 @@ function expandHome(path) {
|
|
|
1871
1938
|
function isRecord2(value) {
|
|
1872
1939
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1873
1940
|
}
|
|
1874
|
-
var PROJECT_SOURCE_SKILL_ROOTS_ENV, TICKET_PROVIDER_ADAPTERS_ENV, PROJECT_REGISTRY_SCHEMA_VERSION, DEFAULT_NEW_PROJECT_STATUS, BOARD_URL_DEPRECATION_WARNING, PROJECT_IDENTIFIER_SOURCES, IDENTIFIER_ASSIGNING_PROVIDERS, TICKET_PROVIDER_STATES, IDENTIFIER_REPAIR_COMMAND, DEFAULT_SOURCE_SKILL_ROOTS, PROJECT_REGISTRY_OWNED_KEYS, PROJECT_NOTEBOOK_OWNED_KEYS, TICKET_PROVIDER_OWNED_KEYS, GLOBAL_NOTEBOOK_OWNED_KEYS, GLOBAL_NOTEBOOK_AUTH_OWNED_KEYS, GLOBAL_NOTEBOOK_DEFAULTS_OWNED_KEYS, GLOBAL_NOTEBOOK_LIMITS_OWNED_KEYS, GLOBAL_NOTEBOOK_SUMMARIZER_OWNED_KEYS, SAFE_PATH_SEGMENT;
|
|
1941
|
+
var PROJECT_SOURCE_SKILL_ROOTS_ENV, TICKET_PROVIDER_ADAPTERS_ENV, PROJECT_REGISTRY_SCHEMA_VERSION, DEFAULT_NEW_PROJECT_STATUS, PROJECT_TYPES, PROJECT_TYPE_LANGUAGES, PROJECT_TYPE_SENTINELS, BOARD_URL_DEPRECATION_WARNING, PROJECT_IDENTIFIER_SOURCES, IDENTIFIER_ASSIGNING_PROVIDERS, TICKET_PROVIDER_STATES, IDENTIFIER_REPAIR_COMMAND, DEFAULT_SOURCE_SKILL_ROOTS, PROJECT_REGISTRY_OWNED_KEYS, PROJECT_NOTEBOOK_OWNED_KEYS, TICKET_PROVIDER_OWNED_KEYS, GLOBAL_NOTEBOOK_OWNED_KEYS, GLOBAL_NOTEBOOK_AUTH_OWNED_KEYS, GLOBAL_NOTEBOOK_DEFAULTS_OWNED_KEYS, GLOBAL_NOTEBOOK_LIMITS_OWNED_KEYS, GLOBAL_NOTEBOOK_SUMMARIZER_OWNED_KEYS, SAFE_PATH_SEGMENT;
|
|
1875
1942
|
var init_project = __esm({
|
|
1876
1943
|
"src/project/index.ts"() {
|
|
1877
1944
|
"use strict";
|
|
@@ -1887,6 +1954,13 @@ var init_project = __esm({
|
|
|
1887
1954
|
TICKET_PROVIDER_ADAPTERS_ENV = "PJ_TICKET_PROVIDER_ADAPTERS";
|
|
1888
1955
|
PROJECT_REGISTRY_SCHEMA_VERSION = 1;
|
|
1889
1956
|
DEFAULT_NEW_PROJECT_STATUS = "active";
|
|
1957
|
+
PROJECT_TYPES = ["base", "obsidian-plugin"];
|
|
1958
|
+
PROJECT_TYPE_LANGUAGES = {
|
|
1959
|
+
"obsidian-plugin": ["typescript", "javascript"]
|
|
1960
|
+
};
|
|
1961
|
+
PROJECT_TYPE_SENTINELS = {
|
|
1962
|
+
"obsidian-plugin": ["manifest.json", join11("src", "main.ts")]
|
|
1963
|
+
};
|
|
1890
1964
|
BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
|
|
1891
1965
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
1892
1966
|
IDENTIFIER_ASSIGNING_PROVIDERS = ["plane", "linear"];
|
|
@@ -7678,9 +7752,24 @@ function lstatIfPresent(path) {
|
|
|
7678
7752
|
}
|
|
7679
7753
|
}
|
|
7680
7754
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
7755
|
+
if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
|
|
7681
7756
|
const packageJson = join5(repoRoot, "package.json");
|
|
7682
7757
|
return existsSync3(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";
|
|
7683
7758
|
}
|
|
7759
|
+
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";
|
|
7760
|
+
function recordedProjectType(ctx) {
|
|
7761
|
+
return record(record(readProjectJson(ctx)?.template).commonproject).project_type;
|
|
7762
|
+
}
|
|
7763
|
+
function isObsidianPluginRepo(ctx, repoRoot) {
|
|
7764
|
+
const recorded = recordedProjectType(ctx);
|
|
7765
|
+
if (recorded !== void 0) return recorded === "obsidian-plugin";
|
|
7766
|
+
const manifest = record(tryParseJson(safeReadText2(join5(repoRoot, "manifest.json"))));
|
|
7767
|
+
return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
7768
|
+
}
|
|
7769
|
+
function unsupportedVersionTypes(conf, script) {
|
|
7770
|
+
const types = conf.split("\n").map((line) => line.trim().split(/\s+/)[0] ?? "").filter((type) => type && !type.startsWith("#"));
|
|
7771
|
+
return [...new Set(types)].filter((type) => !new RegExp(`(^|[\\s|])${type.replace(/[^a-z0-9-]/gi, "")}(\\|[a-z-]+)*\\)`, "m").test(script));
|
|
7772
|
+
}
|
|
7684
7773
|
var BASE_MISE_PATH_ENTRIES = [".mise/scripts", "agents/hermes/pm"];
|
|
7685
7774
|
function requiredMisePathEntries(_ctx) {
|
|
7686
7775
|
return [...BASE_MISE_PATH_ENTRIES];
|
|
@@ -8338,8 +8427,9 @@ function projectJsonFinding(ctx) {
|
|
|
8338
8427
|
};
|
|
8339
8428
|
}
|
|
8340
8429
|
var BMAD_NPM_PACKAGE = "bmad-method";
|
|
8341
|
-
var BMAD_INSTALLER_VERSION = "6.12.
|
|
8430
|
+
var BMAD_INSTALLER_VERSION = "6.12.1";
|
|
8342
8431
|
var BMAD_TARGET_CHANNEL = "next";
|
|
8432
|
+
var BMAD_STABLE_CHANNEL = "latest";
|
|
8343
8433
|
var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
8344
8434
|
var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
|
|
8345
8435
|
var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
|
|
@@ -8533,10 +8623,17 @@ function runBmadInstall(repoRoot, modules = selectedBmadModules(repoRoot), versi
|
|
|
8533
8623
|
const invocation = bmadInstallerInvocation(version);
|
|
8534
8624
|
const result2 = spawnSync3(invocation.command, [...invocation.prefixArgs, ...bmadInstallerArgs(repoRoot, modules)], { encoding: "utf8" });
|
|
8535
8625
|
if (result2.status !== 0) {
|
|
8536
|
-
|
|
8626
|
+
const stdoutTail = result2.stdout?.trim().split("\n").slice(-12).join("\n");
|
|
8627
|
+
return { ok: false, error: result2.stderr?.trim() || stdoutTail || result2.error?.message || `installer exited ${result2.status}` };
|
|
8537
8628
|
}
|
|
8538
8629
|
return { ok: true };
|
|
8539
8630
|
}
|
|
8631
|
+
function bmadChannelTarget(distTags) {
|
|
8632
|
+
const next = distTags?.[BMAD_TARGET_CHANNEL];
|
|
8633
|
+
const stable = distTags?.[BMAD_STABLE_CHANNEL];
|
|
8634
|
+
if (next && stable && compareBmadVersions(stable, next) > 0) return { version: stable, channel: BMAD_STABLE_CHANNEL };
|
|
8635
|
+
return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
|
|
8636
|
+
}
|
|
8540
8637
|
function readInstalledBmadVersion(repoRoot) {
|
|
8541
8638
|
const raw = safeReadText2(join5(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
8542
8639
|
if (!raw) return void 0;
|
|
@@ -8884,6 +8981,15 @@ function createMiseChecks() {
|
|
|
8884
8981
|
if (!text?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
8885
8982
|
if (!existsSync3(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
8886
8983
|
if (!existsSync3(manifestPath)) details.push(".mise/version-files.conf missing");
|
|
8984
|
+
const conf = safeReadText2(manifestPath);
|
|
8985
|
+
const script = safeReadText2(versioningPath);
|
|
8986
|
+
if (conf !== null && script !== null) {
|
|
8987
|
+
const unsupported = unsupportedVersionTypes(conf, script);
|
|
8988
|
+
if (unsupported.length) details.push(`.mise/scripts/versioning.sh cannot version type(s) ${unsupported.join(", ")} named in .mise/version-files.conf`);
|
|
8989
|
+
}
|
|
8990
|
+
if (conf !== null && isObsidianPluginRepo(ctx, ctx.repoRoot) && !/^obsidian\s+manifest\.json\s*$/m.test(conf)) {
|
|
8991
|
+
details.push(".mise/version-files.conf does not version manifest.json/versions.json for this obsidian-plugin project");
|
|
8992
|
+
}
|
|
8887
8993
|
return {
|
|
8888
8994
|
id: "mise.versioning",
|
|
8889
8995
|
title: "managed mise versioning block",
|
|
@@ -9425,6 +9531,15 @@ function strayOpInjectLines(text) {
|
|
|
9425
9531
|
}
|
|
9426
9532
|
return [...new Set(lines)].sort((a, b) => a - b);
|
|
9427
9533
|
}
|
|
9534
|
+
var COPIER_ANSWERS_HEADER = "# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY";
|
|
9535
|
+
function parseCopierAnswers(text) {
|
|
9536
|
+
try {
|
|
9537
|
+
const parsed = YAML2.parse(text);
|
|
9538
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { answers: parsed } : { problem: ".copier-answers.yml is not a YAML mapping" };
|
|
9539
|
+
} catch (error) {
|
|
9540
|
+
return { problem: `.copier-answers.yml is not valid YAML (${errorText(error).split("\n")[0]})` };
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9428
9543
|
function createProjectProvenanceChecks() {
|
|
9429
9544
|
return [
|
|
9430
9545
|
{
|
|
@@ -9438,16 +9553,14 @@ function createProjectProvenanceChecks() {
|
|
|
9438
9553
|
if (!text) {
|
|
9439
9554
|
details.push(".copier-answers.yml missing");
|
|
9440
9555
|
} else {
|
|
9441
|
-
if (!text.startsWith(
|
|
9442
|
-
|
|
9443
|
-
if (
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
const yamlDesc = descMatch?.[1]?.replace(/\n\s+/g, " ").trim() ?? "";
|
|
9450
|
-
if (yamlDesc !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
9556
|
+
if (!text.startsWith(COPIER_ANSWERS_HEADER)) details.push("missing Copier overwrite warning header");
|
|
9557
|
+
const parsed = parseCopierAnswers(text);
|
|
9558
|
+
if ("problem" in parsed) details.push(parsed.problem);
|
|
9559
|
+
else {
|
|
9560
|
+
const { answers } = parsed;
|
|
9561
|
+
if (answers._src_path === void 0) details.push("_src_path missing");
|
|
9562
|
+
if (project?.project_name && String(answers.project_name ?? "") !== String(project.project_name)) details.push("project_name drift between .copier-answers.yml and .project.json");
|
|
9563
|
+
if (project?.project_description && String(answers.project_description ?? "") !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
9451
9564
|
}
|
|
9452
9565
|
}
|
|
9453
9566
|
return {
|
|
@@ -9462,14 +9575,18 @@ function createProjectProvenanceChecks() {
|
|
|
9462
9575
|
migrate: (ctx, finding2) => {
|
|
9463
9576
|
const changedFiles = [];
|
|
9464
9577
|
const project = canonicalProjectJson(ctx);
|
|
9465
|
-
const text = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
9466
|
-
_src_path: ${join5(ctx.pjanglerRoot, "templates", "commonproject")}
|
|
9467
|
-
project_description: ${String(project.project_description)}
|
|
9468
|
-
project_name: ${String(project.project_name)}
|
|
9469
|
-
ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
9470
|
-
`;
|
|
9471
9578
|
const path = join5(ctx.repoRoot, ".copier-answers.yml");
|
|
9472
|
-
|
|
9579
|
+
const current = safeReadText2(path);
|
|
9580
|
+
const existing = current ? YAML2.parseDocument(current) : void 0;
|
|
9581
|
+
const document = existing && !existing.errors.length && YAML2.isMap(existing.contents) ? existing : new YAML2.Document({});
|
|
9582
|
+
document.set("_src_path", join5(ctx.pjanglerRoot, "templates", "commonproject"));
|
|
9583
|
+
document.set("project_description", String(project.project_description));
|
|
9584
|
+
document.set("project_name", String(project.project_name));
|
|
9585
|
+
document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
|
|
9586
|
+
const body = String(document);
|
|
9587
|
+
const text = body.startsWith(COPIER_ANSWERS_HEADER) ? body : `${COPIER_ANSWERS_HEADER}
|
|
9588
|
+
${body}`;
|
|
9589
|
+
if (current !== text) {
|
|
9473
9590
|
changedFiles.push(path);
|
|
9474
9591
|
if (!ctx.dryRun) writeText(path, text);
|
|
9475
9592
|
}
|
|
@@ -9701,7 +9818,9 @@ function createBmadChecks() {
|
|
|
9701
9818
|
}
|
|
9702
9819
|
const pinned = ctx.bmadVersionPin?.trim();
|
|
9703
9820
|
const resolved = pinned ? void 0 : resolveBmadDistTags(ctx.homeDir);
|
|
9704
|
-
const
|
|
9821
|
+
const channelTarget = pinned ? void 0 : bmadChannelTarget(resolved?.distTags);
|
|
9822
|
+
const available = pinned ?? channelTarget?.version;
|
|
9823
|
+
const channel = channelTarget?.channel ?? BMAD_TARGET_CHANNEL;
|
|
9705
9824
|
if (!available) {
|
|
9706
9825
|
return {
|
|
9707
9826
|
id: "bmad.version",
|
|
@@ -9712,7 +9831,7 @@ function createBmadChecks() {
|
|
|
9712
9831
|
fixable: false
|
|
9713
9832
|
};
|
|
9714
9833
|
}
|
|
9715
|
-
const targetLabel = pinned ? `pinned ${available}` : `${
|
|
9834
|
+
const targetLabel = pinned ? `pinned ${available}` : `${channel} ${available}`;
|
|
9716
9835
|
const staleNote = resolved?.stale ? ` ${glyph.dot} cached` : "";
|
|
9717
9836
|
const comparison = compareBmadVersions(installed, available);
|
|
9718
9837
|
if (pinned ? comparison === 0 : comparison >= 0) {
|
|
@@ -9733,7 +9852,7 @@ function createBmadChecks() {
|
|
|
9733
9852
|
title: "BMAD version currency",
|
|
9734
9853
|
status: "skip",
|
|
9735
9854
|
summary: `BMAD ${installed} installed; stable currency floor unknown (${BMAD_NPM_PACKAGE} latest unresolved)`,
|
|
9736
|
-
details: [`installed: ${installed}`, `available: ${available} (${BMAD_NPM_PACKAGE}@${
|
|
9855
|
+
details: [`installed: ${installed}`, `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`],
|
|
9737
9856
|
fixable: false
|
|
9738
9857
|
};
|
|
9739
9858
|
}
|
|
@@ -9746,7 +9865,7 @@ function createBmadChecks() {
|
|
|
9746
9865
|
summary: `BMAD ${installed} is at or ahead of stable ${stable ?? "unknown"}; ${targetLabel} available${staleNote}`,
|
|
9747
9866
|
details: [
|
|
9748
9867
|
`installed: ${installed}`,
|
|
9749
|
-
`available: ${available} (${BMAD_NPM_PACKAGE}@${
|
|
9868
|
+
`available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`,
|
|
9750
9869
|
stable ? `stable latest: ${stable}` : "",
|
|
9751
9870
|
"run `pj migrate bmad.version` to take the prerelease"
|
|
9752
9871
|
].filter(Boolean),
|
|
@@ -9760,7 +9879,7 @@ function createBmadChecks() {
|
|
|
9760
9879
|
summary: pinnedMismatch ? `BMAD ${installed} does not match ${targetLabel}` : `BMAD ${installed} is behind stable ${stable} \u2014 upgrade available`,
|
|
9761
9880
|
details: [
|
|
9762
9881
|
`installed: ${installed}`,
|
|
9763
|
-
pinned ? `required transaction pin: ${available}` : `available: ${available} (${BMAD_NPM_PACKAGE}@${
|
|
9882
|
+
pinned ? `required transaction pin: ${available}` : `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`,
|
|
9764
9883
|
!pinned && stable ? `stable latest: ${stable}` : "",
|
|
9765
9884
|
"run `pj migrate bmad.version` to upgrade"
|
|
9766
9885
|
].filter(Boolean),
|
|
@@ -9773,13 +9892,13 @@ function createBmadChecks() {
|
|
|
9773
9892
|
}
|
|
9774
9893
|
{
|
|
9775
9894
|
const current = readInstalledBmadVersion(ctx.repoRoot);
|
|
9776
|
-
const target = ctx.bmadVersionPin?.trim() ?? resolveBmadDistTags(ctx.homeDir)?.distTags?.
|
|
9895
|
+
const target = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
9777
9896
|
if (!current || !target || compareBmadVersions(current, target) === 0) {
|
|
9778
9897
|
return { id: finding2.id, title: finding2.title, status: "noop", summary: "BMAD already current", changedFiles: [], details: [] };
|
|
9779
9898
|
}
|
|
9780
9899
|
}
|
|
9781
9900
|
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
9782
|
-
const available = ctx.bmadVersionPin?.trim() ?? resolveBmadDistTags(ctx.homeDir)?.distTags?.
|
|
9901
|
+
const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
9783
9902
|
const manifestPath = join5(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
9784
9903
|
const manifestSelection = manifestBmadModules(ctx.repoRoot);
|
|
9785
9904
|
if (manifestSelection.status === "invalid") {
|
|
@@ -14427,6 +14546,7 @@ var server = new McpServer({
|
|
|
14427
14546
|
version: PJANGLER_VERSION
|
|
14428
14547
|
});
|
|
14429
14548
|
var TICKET_PROVIDER_SCHEMA = z.enum(["plane", "trello"]);
|
|
14549
|
+
var PROJECT_TYPE_SCHEMA = z.enum(PROJECT_TYPES).describe("What CommonProject renders: base (default) or obsidian-plugin. Fixed at creation; omit it to keep the recorded type.");
|
|
14430
14550
|
var BOARD_URL_COMPAT_SCHEMA = z.string().optional().describe("Deprecated compatibility input. Ignored; board URLs are derived at runtime and are never persisted.").meta({ deprecated: true });
|
|
14431
14551
|
var RUNTIME_REPO_COMPAT_SCHEMA = z.boolean().optional().describe("Deprecated no-op. Hermes always converges ignored role-local runtime state and never provisions a per-agent GitHub repository.").meta({ deprecated: true });
|
|
14432
14552
|
function safePathSegmentSchema(label) {
|
|
@@ -14748,6 +14868,7 @@ server.registerTool(
|
|
|
14748
14868
|
planeProjectId: z.string().optional(),
|
|
14749
14869
|
projectIdentifier: z.string().optional(),
|
|
14750
14870
|
primaryLanguage: z.string().optional(),
|
|
14871
|
+
projectType: PROJECT_TYPE_SCHEMA.optional(),
|
|
14751
14872
|
skipPlane: z.boolean().optional(),
|
|
14752
14873
|
agentPurpose: z.string().optional(),
|
|
14753
14874
|
local: z.boolean().optional(),
|
|
@@ -14792,7 +14913,8 @@ server.registerTool(
|
|
|
14792
14913
|
targetDir,
|
|
14793
14914
|
projectSlug,
|
|
14794
14915
|
sourceSkill: input.sourceSkill,
|
|
14795
|
-
primaryLanguage: input.primaryLanguage
|
|
14916
|
+
primaryLanguage: input.primaryLanguage,
|
|
14917
|
+
projectType: input.projectType,
|
|
14796
14918
|
apply: !dryRun,
|
|
14797
14919
|
live: input.live ?? false,
|
|
14798
14920
|
provisionTicketBoard: externalEffects.ticketBoard,
|
|
@@ -14857,6 +14979,7 @@ server.registerTool(
|
|
|
14857
14979
|
targetDir: z.string().optional(),
|
|
14858
14980
|
sourceSkill: z.string().optional(),
|
|
14859
14981
|
primaryLanguage: z.string().optional(),
|
|
14982
|
+
projectType: PROJECT_TYPE_SCHEMA.optional(),
|
|
14860
14983
|
apply: z.boolean().optional(),
|
|
14861
14984
|
live: z.boolean().optional(),
|
|
14862
14985
|
provisionTicketBoard: z.boolean().optional().describe("Explicitly opt in to ticket-board provisioning; also requires live=true and skipPlane!=true."),
|
|
@@ -14880,6 +15003,7 @@ server.registerTool(
|
|
|
14880
15003
|
targetDir: input.targetDir,
|
|
14881
15004
|
sourceSkill: input.sourceSkill,
|
|
14882
15005
|
primaryLanguage: input.primaryLanguage,
|
|
15006
|
+
projectType: input.projectType,
|
|
14883
15007
|
apply: input.apply ?? false,
|
|
14884
15008
|
live: input.live ?? false,
|
|
14885
15009
|
provisionTicketBoard: externalEffects.ticketBoard,
|
|
@@ -660,7 +660,7 @@ function validateProjectRecord(project, key) {
|
|
|
660
660
|
function isRecord2(value) {
|
|
661
661
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
662
662
|
}
|
|
663
|
-
var PROJECT_REGISTRY_SCHEMA_VERSION, PROJECT_IDENTIFIER_SOURCES, IDENTIFIER_ASSIGNING_PROVIDERS, TICKET_PROVIDER_STATES, IDENTIFIER_REPAIR_COMMAND, DEFAULT_SOURCE_SKILL_ROOTS, PROJECT_REGISTRY_OWNED_KEYS, PROJECT_NOTEBOOK_OWNED_KEYS, TICKET_PROVIDER_OWNED_KEYS, GLOBAL_NOTEBOOK_OWNED_KEYS, GLOBAL_NOTEBOOK_AUTH_OWNED_KEYS, GLOBAL_NOTEBOOK_DEFAULTS_OWNED_KEYS, GLOBAL_NOTEBOOK_LIMITS_OWNED_KEYS, GLOBAL_NOTEBOOK_SUMMARIZER_OWNED_KEYS, SAFE_PATH_SEGMENT;
|
|
663
|
+
var PROJECT_REGISTRY_SCHEMA_VERSION, PROJECT_TYPE_SENTINELS, PROJECT_IDENTIFIER_SOURCES, IDENTIFIER_ASSIGNING_PROVIDERS, TICKET_PROVIDER_STATES, IDENTIFIER_REPAIR_COMMAND, DEFAULT_SOURCE_SKILL_ROOTS, PROJECT_REGISTRY_OWNED_KEYS, PROJECT_NOTEBOOK_OWNED_KEYS, TICKET_PROVIDER_OWNED_KEYS, GLOBAL_NOTEBOOK_OWNED_KEYS, GLOBAL_NOTEBOOK_AUTH_OWNED_KEYS, GLOBAL_NOTEBOOK_DEFAULTS_OWNED_KEYS, GLOBAL_NOTEBOOK_LIMITS_OWNED_KEYS, GLOBAL_NOTEBOOK_SUMMARIZER_OWNED_KEYS, SAFE_PATH_SEGMENT;
|
|
664
664
|
var init_project = __esm({
|
|
665
665
|
"src/project/index.ts"() {
|
|
666
666
|
"use strict";
|
|
@@ -673,6 +673,9 @@ var init_project = __esm({
|
|
|
673
673
|
init_boardQuery();
|
|
674
674
|
init_registryClient();
|
|
675
675
|
PROJECT_REGISTRY_SCHEMA_VERSION = 1;
|
|
676
|
+
PROJECT_TYPE_SENTINELS = {
|
|
677
|
+
"obsidian-plugin": ["manifest.json", join2("src", "main.ts")]
|
|
678
|
+
};
|
|
676
679
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
677
680
|
IDENTIFIER_ASSIGNING_PROVIDERS = ["plane", "linear"];
|
|
678
681
|
TICKET_PROVIDER_STATES = ["planned", "linked", "skipped"];
|
|
@@ -234,7 +234,7 @@ function validateManifestFields(manifest, path) {
|
|
|
234
234
|
if (!object(manifest.template.commonproject)) fail("template.commonproject", "an object");
|
|
235
235
|
const template = manifest.template.commonproject;
|
|
236
236
|
if (template.enabled !== void 0 && typeof template.enabled !== "boolean") fail("template.commonproject.enabled", "boolean");
|
|
237
|
-
strings(template, ["primary_language"], "template.commonproject.");
|
|
237
|
+
strings(template, ["primary_language", "project_type"], "template.commonproject.");
|
|
238
238
|
}
|
|
239
239
|
for (const field of ["binding", "policy"]) if (manifest.notebook?.[field] !== void 0 && !object(manifest.notebook[field])) fail(`notebook.${field}`, "an object");
|
|
240
240
|
if (manifest.notebook?.binding) strings(manifest.notebook.binding, ["provider", "state", "notebook_name", "blocked_reason", "notebook_id", "overview_note_id", "created_at", "updated_at"], "notebook.binding.");
|
package/package.json
CHANGED
|
@@ -43,6 +43,30 @@ ticket_provider:
|
|
|
43
43
|
Plane: plane
|
|
44
44
|
Trello: trello
|
|
45
45
|
|
|
46
|
+
# What this project is. `base` is the generic skeleton below and nothing else.
|
|
47
|
+
# `obsidian-plugin` (`pj init --obsidian-plugin`) adds an Obsidian community
|
|
48
|
+
# plugin on top of it, vendored from the canonical starter
|
|
49
|
+
# obsidianmd/obsidian-sample-plugin @ 07ceb81d (2026-08-02): npm + esbuild +
|
|
50
|
+
# TypeScript, manifest.json/versions.json, eslint-plugin-obsidianmd, and its
|
|
51
|
+
# build/release workflows moved to the self-hosted runners.
|
|
52
|
+
#
|
|
53
|
+
# Every type-only path under template/ carries `{% if is_obsidian_plugin %}`
|
|
54
|
+
# in its NAME (directories included), so it does not exist in a base render.
|
|
55
|
+
# Never name one a bare `.gitignore`/`.npmrc`: npm pack drops those from the
|
|
56
|
+
# published pjangler tarball.
|
|
57
|
+
#
|
|
58
|
+
# A real question on purpose, not `when: false`. Copier records only asked
|
|
59
|
+
# questions in .copier-answers.yml; a hidden type would be forgotten, and a
|
|
60
|
+
# re-copy would silently re-render the shared files as `base`. `--defaults`
|
|
61
|
+
# never prompts for it, and pjangler always passes it with --data.
|
|
62
|
+
project_type:
|
|
63
|
+
type: str
|
|
64
|
+
help: What kind of project is this?
|
|
65
|
+
default: base
|
|
66
|
+
choices:
|
|
67
|
+
CommonProject base: base
|
|
68
|
+
Obsidian community plugin: obsidian-plugin
|
|
69
|
+
|
|
46
70
|
# --- Derived (never asked) ---
|
|
47
71
|
|
|
48
72
|
project_slug:
|
|
@@ -50,6 +74,70 @@ project_slug:
|
|
|
50
74
|
default: "{{ project_name | lower | replace(' ', '-') | replace('_', '-') }}"
|
|
51
75
|
when: false
|
|
52
76
|
|
|
77
|
+
is_obsidian_plugin:
|
|
78
|
+
type: bool
|
|
79
|
+
default: "{{ project_type == 'obsidian-plugin' }}"
|
|
80
|
+
when: false
|
|
81
|
+
|
|
82
|
+
# Obsidian manifest identity (obsidian-plugin only). The catalog rejects a
|
|
83
|
+
# plugin id or name containing "obsidian" and frowns on a trailing "plugin",
|
|
84
|
+
# and the id must equal the plugin's folder name in a vault, so both are the
|
|
85
|
+
# project's own identity with those affixes dropped:
|
|
86
|
+
# "obsidian-tag-wrangler" -> id "tag-wrangler".
|
|
87
|
+
plugin_id:
|
|
88
|
+
type: str
|
|
89
|
+
default: >-
|
|
90
|
+
{%- set id = project_slug | lower | regex_replace('[^a-z0-9-]+', '-') | replace('obsidian', '') | regex_replace('-+', '-') -%}
|
|
91
|
+
{%- set id = id.strip('-') -%}
|
|
92
|
+
{%- if id.endswith('-plugin') %}{% set id = id[:-7] %}{% endif -%}
|
|
93
|
+
{{ id or 'plugin' }}
|
|
94
|
+
when: false
|
|
95
|
+
|
|
96
|
+
# A slug-style project name ("obsidian-tag-wrangler") reads as words in the
|
|
97
|
+
# Obsidian UI: separators become spaces and an all-lowercase name is
|
|
98
|
+
# title-cased ("Tag Wrangler"); a name with its own casing keeps it.
|
|
99
|
+
plugin_name:
|
|
100
|
+
type: str
|
|
101
|
+
default: >-
|
|
102
|
+
{%- set name = project_name | regex_replace('(?i)obsidian', ' ') | regex_replace('[-_:.]+', ' ') | regex_replace('\\s+', ' ') | trim -%}
|
|
103
|
+
{%- set name = name | regex_replace('(?i)\\s*\\bplugin$', '') | trim -%}
|
|
104
|
+
{%- if name == name | lower %}{% set name = name | title %}{% endif -%}
|
|
105
|
+
{{ name or project_name }}
|
|
106
|
+
when: false
|
|
107
|
+
|
|
108
|
+
# TypeScript identifier stem for src/: "tag-wrangler" -> TagWranglerPlugin,
|
|
109
|
+
# TagWranglerSettings, TagWranglerSettingTab, TagWranglerModal. The starter's
|
|
110
|
+
# own names (MyPlugin, SampleModal, ...) fail eslint-plugin-obsidianmd's
|
|
111
|
+
# sample-names rule, and a "Plugin" stem would declare PluginSettingTab over
|
|
112
|
+
# Obsidian's own, so those stems get a "Project" suffix.
|
|
113
|
+
plugin_class:
|
|
114
|
+
type: str
|
|
115
|
+
default: >-
|
|
116
|
+
{%- set ns = namespace(name='') -%}
|
|
117
|
+
{%- for word in plugin_id.replace('_', '-').replace('.', '-').split('-') -%}
|
|
118
|
+
{%- set ns.name = ns.name ~ (word | capitalize) -%}
|
|
119
|
+
{%- endfor -%}
|
|
120
|
+
{%- if not ns.name or ns.name[0] in '0123456789' %}{% set ns.name = 'Plugin' ~ ns.name %}{% endif -%}
|
|
121
|
+
{%- if ns.name in ['My', 'Sample', 'Plugin'] %}{% set ns.name = ns.name ~ 'Project' %}{% endif -%}
|
|
122
|
+
{{ ns.name }}
|
|
123
|
+
when: false
|
|
124
|
+
|
|
125
|
+
# The catalog wants a sentence: it must end in punctuation.
|
|
126
|
+
plugin_description:
|
|
127
|
+
type: str
|
|
128
|
+
default: >-
|
|
129
|
+
{%- set text = project_description | trim -%}
|
|
130
|
+
{%- if text and text[-1] not in '.?!)' %}{% set text = text ~ '.' %}{% endif -%}
|
|
131
|
+
{{ text or 'Describe what this plugin does.' }}
|
|
132
|
+
when: false
|
|
133
|
+
|
|
134
|
+
# pjangler passes the operator's git user.name; a bare copier render leaves it
|
|
135
|
+
# for the author to fill in.
|
|
136
|
+
plugin_author:
|
|
137
|
+
type: str
|
|
138
|
+
default: ""
|
|
139
|
+
when: false
|
|
140
|
+
|
|
53
141
|
# Board binding — supplied by init-project.sh's --data-file after the board is
|
|
54
142
|
# created. Defaults keep a bare `copier copy` rendering valid (empty binding).
|
|
55
143
|
plane_workspace:
|
|
@@ -111,6 +199,19 @@ _skip_if_exists:
|
|
|
111
199
|
# Selection is project intent, not generated output. A refresh must preserve
|
|
112
200
|
# inheritance choices, exclusions, and active packs byte-for-byte.
|
|
113
201
|
- ".agents/skills.json"
|
|
202
|
+
# obsidian-plugin seed files: rendered once at creation, owned by the plugin
|
|
203
|
+
# after that. A forced re-render must not reset a bumped manifest version or
|
|
204
|
+
# replace real plugin code with the sample. (Paths a base render never
|
|
205
|
+
# produces, so this is inert for base projects.)
|
|
206
|
+
- "package.json"
|
|
207
|
+
- "package-lock.json"
|
|
208
|
+
- "manifest.json"
|
|
209
|
+
- "versions.json"
|
|
210
|
+
- "styles.css"
|
|
211
|
+
- "tsconfig.json"
|
|
212
|
+
- "esbuild.config.mjs"
|
|
213
|
+
- "eslint.config.mts"
|
|
214
|
+
- "src/**"
|
|
114
215
|
|
|
115
216
|
# --- Post-generation tasks ---
|
|
116
217
|
#
|
|
@@ -124,7 +225,7 @@ _tasks:
|
|
|
124
225
|
# byte and append only the small CommonProject-owned contract when absent.
|
|
125
226
|
# Machine-local backup/editor/CLI exclusions belong in the user's global
|
|
126
227
|
# ignore; `.agents/` is the sole canonical agent-config tree in Git.
|
|
127
|
-
- "touch .gitignore; if [ -s .gitignore ] && [ -n \"$(tail -c 1 .gitignore)\" ]; then printf '\\n' >> .gitignore; fi; grep -Fqx '# CommonProject repository contract' .gitignore 2>/dev/null || printf '%s\\n' '# CommonProject repository contract' >> .gitignore; for pattern in '.env' '.env.*' '!.env.op' '/.agents/local.json' '/.agents/skills'; do grep -Fqx -- \"$pattern\" .gitignore 2>/dev/null || printf '%s\\n' \"$pattern\" >> .gitignore; done"
|
|
228
|
+
- "touch .gitignore; if [ -s .gitignore ] && [ -n \"$(tail -c 1 .gitignore)\" ]; then printf '\\n' >> .gitignore; fi; grep -Fqx '# CommonProject repository contract' .gitignore 2>/dev/null || printf '%s\\n' '# CommonProject repository contract' >> .gitignore; for pattern in '.env' '.env.*' '!.env.op' '/.agents/local.json' '/.agents/skills'{% if is_obsidian_plugin %} 'node_modules/' '/main.js' '*.map' '/data.json'{% endif %}; do grep -Fqx -- \"$pattern\" .gitignore 2>/dev/null || printf '%s\\n' \"$pattern\" >> .gitignore; done"
|
|
128
229
|
# PJAN-82: use the managed script, which refuses to replace a REAL CLAUDE.md.
|
|
129
230
|
# `ln -sf` cannot distinguish a stale link it owns from a hand-written file it
|
|
130
231
|
# must not touch, so a re-render into a live project destroyed the file and
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Build + lint the Obsidian plugin on every push and pull request.
|
|
2
|
+
#
|
|
3
|
+
# Adapted from obsidianmd/obsidian-sample-plugin's lint.yml. The one deliberate
|
|
4
|
+
# change is the runner: self-hosted on big-chungus, never a GitHub-hosted label.
|
|
5
|
+
# Hosted minutes are billed on private repos and the budget is $0. A
|
|
6
|
+
# personal-account repo needs its own repo-level runner (delonet-conventions
|
|
7
|
+
# skill, references/ci-runners.md).
|
|
8
|
+
name: Plugin build
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: ['**']
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: ['**']
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: [self-hosted, Linux, delonet]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v6
|
|
22
|
+
- name: Use Node.js 24
|
|
23
|
+
uses: actions/setup-node@v6
|
|
24
|
+
with:
|
|
25
|
+
node-version: 24
|
|
26
|
+
cache: 'npm'
|
|
27
|
+
- run: npm ci
|
|
28
|
+
- run: npm run build --if-present
|
|
29
|
+
- run: npm run lint
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Draft a GitHub release for every pushed tag.
|
|
2
|
+
#
|
|
3
|
+
# Adapted from obsidianmd/obsidian-sample-plugin's release.yml. Changes:
|
|
4
|
+
# - self-hosted runner (see lint.yml for why);
|
|
5
|
+
# - build provenance is attested only on public repos. GitHub refuses
|
|
6
|
+
# artifact attestations on user-owned private repos, and an Obsidian plugin
|
|
7
|
+
# only has to be public once it is submitted to the community catalog.
|
|
8
|
+
#
|
|
9
|
+
# Tag with the bare manifest version (`npm version patch` does this — .npmrc
|
|
10
|
+
# sets tag-version-prefix=""), never a leading `v`: Obsidian matches the release
|
|
11
|
+
# tag against manifest.json's version.
|
|
12
|
+
name: Release Obsidian plugin
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags:
|
|
17
|
+
- '*'
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
build:
|
|
21
|
+
runs-on: [self-hosted, Linux, delonet]
|
|
22
|
+
permissions:
|
|
23
|
+
contents: write
|
|
24
|
+
id-token: write
|
|
25
|
+
attestations: write
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v6
|
|
28
|
+
|
|
29
|
+
- name: Use Node.js
|
|
30
|
+
uses: actions/setup-node@v6
|
|
31
|
+
with:
|
|
32
|
+
node-version: 24
|
|
33
|
+
cache: 'npm'
|
|
34
|
+
|
|
35
|
+
- name: Build plugin
|
|
36
|
+
run: |
|
|
37
|
+
npm ci
|
|
38
|
+
npm run build
|
|
39
|
+
|
|
40
|
+
- name: Check for optional styles
|
|
41
|
+
id: styles
|
|
42
|
+
run: |
|
|
43
|
+
[ -f styles.css ] && echo "exists=true" >> "$GITHUB_OUTPUT" || echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
44
|
+
|
|
45
|
+
- name: Attest build provenance
|
|
46
|
+
if: ${{ !github.event.repository.private }}
|
|
47
|
+
uses: actions/attest@v4
|
|
48
|
+
with:
|
|
49
|
+
subject-path: |
|
|
50
|
+
main.js
|
|
51
|
+
${{ steps.styles.outputs.exists == 'true' && 'styles.css' || '' }}
|
|
52
|
+
|
|
53
|
+
- name: Create release
|
|
54
|
+
env:
|
|
55
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
56
|
+
run: |
|
|
57
|
+
tag="${GITHUB_REF#refs/tags/}"
|
|
58
|
+
|
|
59
|
+
gh release create "$tag" \
|
|
60
|
+
--title="$tag" \
|
|
61
|
+
--draft \
|
|
62
|
+
main.js manifest.json ${{ steps.styles.outputs.exists == 'true' && 'styles.css' || '' }}
|