@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
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
# versioning.sh files List the manifest
|
|
17
17
|
#
|
|
18
18
|
# Manifest: .mise/version-files.conf — lines of "<type> <path>" (see file-types).
|
|
19
|
-
# types: json toml cargo csproj gradle plain gittag
|
|
19
|
+
# types: json toml cargo csproj gradle plain gittag obsidian
|
|
20
20
|
#
|
|
21
21
|
# Storage format per type:
|
|
22
22
|
# json/toml/cargo/csproj/gradle/plain -> bare "X.Y.Z"
|
|
23
|
+
# obsidian (manifest.json) -> bare "X.Y.Z", plus the sibling
|
|
24
|
+
# versions.json gains X.Y.Z -> minAppVersion
|
|
23
25
|
# gittag -> tag "vX.Y.Z"
|
|
24
26
|
# `current` always prints with the leading "v".
|
|
25
27
|
|
|
@@ -85,12 +87,21 @@ manifest_entries() {
|
|
|
85
87
|
done <"$MANIFEST"
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
# Rewrite a JSON file through jq, keeping its indentation: tab-indented files
|
|
91
|
+
# (Obsidian's manifest.json/versions.json/package.json) stay tab-indented.
|
|
92
|
+
jq_rewrite() { # $1=path, remaining args go to jq
|
|
93
|
+
local path="$1" tmp indent=(--indent 2); shift
|
|
94
|
+
grep -q $'^\t' "$path" && indent=(--tab)
|
|
95
|
+
tmp="$(mktemp)"
|
|
96
|
+
jq "${indent[@]}" "$@" "$path" >"$tmp" && mv "$tmp" "$path"
|
|
97
|
+
}
|
|
98
|
+
|
|
88
99
|
# ---- per-type read -----------------------------------------------------------
|
|
89
100
|
|
|
90
101
|
read_version() { # $1=type $2=path -> bare X.Y.Z on stdout, or nothing
|
|
91
102
|
local type="$1" path="$2" raw=""
|
|
92
103
|
case "$type" in
|
|
93
|
-
json)
|
|
104
|
+
json|obsidian)
|
|
94
105
|
[[ -f "$path" ]] || return 0
|
|
95
106
|
raw="$(jq -r '.version // empty' "$path" 2>/dev/null || true)" ;;
|
|
96
107
|
toml|cargo)
|
|
@@ -119,12 +130,22 @@ read_version() { # $1=type $2=path -> bare X.Y.Z on stdout, or nothing
|
|
|
119
130
|
# ---- per-type write ----------------------------------------------------------
|
|
120
131
|
|
|
121
132
|
write_version() { # $1=type $2=path $3=bare-new-version
|
|
122
|
-
local type="$1" path="$2" new="$3"
|
|
133
|
+
local type="$1" path="$2" new="$3"
|
|
123
134
|
case "$type" in
|
|
124
135
|
json)
|
|
125
136
|
[[ -f "$path" ]] || return 0
|
|
126
|
-
|
|
127
|
-
|
|
137
|
+
jq_rewrite "$path" --arg v "$new" '.version = $v' ;;
|
|
138
|
+
obsidian)
|
|
139
|
+
# What the sample plugin's version-bump.mjs does: set the manifest
|
|
140
|
+
# version, then record new -> minAppVersion in versions.json (once).
|
|
141
|
+
[[ -f "$path" ]] || return 0
|
|
142
|
+
jq_rewrite "$path" --arg v "$new" '.version = $v'
|
|
143
|
+
local versions min
|
|
144
|
+
versions="$(dirname "$path")/versions.json"
|
|
145
|
+
min="$(jq -r '.minAppVersion // empty' "$path")"
|
|
146
|
+
if [[ -f "$versions" && -n "$min" ]]; then
|
|
147
|
+
jq_rewrite "$versions" --arg v "$new" --arg m "$min" 'if has($v) then . else .[$v] = $m end'
|
|
148
|
+
fi ;;
|
|
128
149
|
toml|cargo)
|
|
129
150
|
[[ -f "$path" ]] || return 0
|
|
130
151
|
# Replace only the first top-level `version = "..."` line.
|
package/README.md
CHANGED
|
@@ -76,6 +76,31 @@ instead of reading a clean report over nothing.
|
|
|
76
76
|
live catalog. The rules are owned by the recipes in `src/recipes/`, not by a
|
|
77
77
|
central table, so that catalog is the only authority on which ones exist.
|
|
78
78
|
|
|
79
|
+
## Obsidian plugins
|
|
80
|
+
|
|
81
|
+
`pjangler init <name> --obsidian-plugin` does everything a normal init does
|
|
82
|
+
(registry, `.project.json`, board, BMAD, skills, hooks, mise, the init commit).
|
|
83
|
+
The difference is the CommonProject render: it is the variant built on the
|
|
84
|
+
canonical [obsidian-sample-plugin](https://github.com/obsidianmd/obsidian-sample-plugin).
|
|
85
|
+
That means `manifest.json`, `versions.json`, npm + esbuild + TypeScript in `src/`,
|
|
86
|
+
`eslint-plugin-obsidianmd`, and build/release workflows on the self-hosted runners.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pjangler init tag-wrangler --obsidian-plugin --description "Wrangle tags across a vault"
|
|
90
|
+
cd tag-wrangler && npm install && npm run dev
|
|
91
|
+
mise run plugin:link ~/Vaults/Main # symlink into <vault>/.obsidian/plugins/<id>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- The type is recorded as `template.commonproject.project_type` and in
|
|
95
|
+
`.copier-answers.yml`. It is fixed at creation, and re-running `init` keeps it.
|
|
96
|
+
- On an existing repo, the flag only records the type, and only when the repo
|
|
97
|
+
already has an Obsidian `manifest.json`.
|
|
98
|
+
- `mise run version:bump` keeps `package.json`, `manifest.json` and `versions.json`
|
|
99
|
+
in step. It never tags. Release with `npm version patch|minor|major`, which
|
|
100
|
+
commits and then tags a bare `X.Y.Z` (Obsidian matches that), and push the tag.
|
|
101
|
+
- MCP takes the same choice as `projectType: "obsidian-plugin"` on
|
|
102
|
+
`pjangler_project_init` and `pjangler_bootstrap_33god_project`.
|
|
103
|
+
|
|
79
104
|
## Agents
|
|
80
105
|
|
|
81
106
|
pjangler does not hire, provision, or inspect agents. That is Flume's surface —
|
package/dist/index.js
CHANGED
|
@@ -968,6 +968,38 @@ import { homedir as homedir5, tmpdir } from "node:os";
|
|
|
968
968
|
import { basename as basename7, delimiter as delimiter2, dirname as dirname11, isAbsolute as isAbsolute5, join as join12, relative as relative7, resolve as resolve10, sep as sep2, win32 } from "node:path";
|
|
969
969
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
970
970
|
import YAML4 from "yaml";
|
|
971
|
+
function normalizeProjectType(value, label = "Project type") {
|
|
972
|
+
const text2 = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
973
|
+
if (PROJECT_TYPES.includes(text2)) return text2;
|
|
974
|
+
throw new Error(`${label} ${JSON.stringify(value)} is not one of: ${PROJECT_TYPES.join(", ")}`);
|
|
975
|
+
}
|
|
976
|
+
function looksLikeObsidianPlugin(dir) {
|
|
977
|
+
try {
|
|
978
|
+
const manifest = JSON.parse(readFileSync10(join12(dir, "manifest.json"), "utf8"));
|
|
979
|
+
return isRecord3(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
980
|
+
} catch {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
|
|
985
|
+
if (projectType === "base") return;
|
|
986
|
+
const copierYml = join12(pjanglerRoot, "templates", "commonproject", "copier.yml");
|
|
987
|
+
let choices;
|
|
988
|
+
try {
|
|
989
|
+
const parsed = YAML4.parse(readFileSync10(copierYml, "utf8"));
|
|
990
|
+
choices = isRecord3(parsed) && isRecord3(parsed.project_type) ? parsed.project_type.choices : void 0;
|
|
991
|
+
} catch {
|
|
992
|
+
choices = void 0;
|
|
993
|
+
}
|
|
994
|
+
const offered = isRecord3(choices) ? Object.values(choices) : Array.isArray(choices) ? choices : [];
|
|
995
|
+
if (!offered.includes(projectType)) {
|
|
996
|
+
throw new Error(`The CommonProject template at ${dirname11(copierYml)} cannot render project type "${projectType}" (its copier.yml offers no such project_type); update pjangler or its templates/commonproject pin`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function operatorDisplayName(cwd) {
|
|
1000
|
+
const result2 = spawnSync5("git", ["config", "--get", "user.name"], { cwd: existsSync7(cwd) ? cwd : void 0, encoding: "utf8", timeout: 5e3 });
|
|
1001
|
+
return result2.status === 0 ? result2.stdout.trim() : "";
|
|
1002
|
+
}
|
|
971
1003
|
function providerAssignsIdentifiers(type) {
|
|
972
1004
|
return IDENTIFIER_ASSIGNING_PROVIDERS.includes((type ?? "").trim());
|
|
973
1005
|
}
|
|
@@ -1560,6 +1592,26 @@ function planProjectInit(input) {
|
|
|
1560
1592
|
const overwrite = input.overwrite ?? input.force ?? false;
|
|
1561
1593
|
const agents = createSafeRecord(Object.entries(existing?.agents ?? {}));
|
|
1562
1594
|
const scaffold = input.scaffold ?? true;
|
|
1595
|
+
const recordedTypeValue = existing?.template?.commonproject?.project_type;
|
|
1596
|
+
const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
|
|
1597
|
+
const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
|
|
1598
|
+
const freshRender = scaffold && localManifest === void 0 && !existsSync7(join12(targetDir, ".copier-answers.yml"));
|
|
1599
|
+
if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
|
|
1600
|
+
const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
|
|
1601
|
+
if (!adoptingPlugin) {
|
|
1602
|
+
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)`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
const projectType = requestedType ?? recordedType ?? "base";
|
|
1606
|
+
const typeLanguages = PROJECT_TYPE_LANGUAGES[projectType];
|
|
1607
|
+
if (input.primaryLanguage !== void 0 && typeLanguages && !typeLanguages.includes(input.primaryLanguage)) {
|
|
1608
|
+
throw new Error(`A ${projectType} project is written in ${typeLanguages.join(" or ")}, not ${input.primaryLanguage}`);
|
|
1609
|
+
}
|
|
1610
|
+
const recordedLanguageValue = (localManifest ? localManifest.template : indexed?.template)?.commonproject?.primary_language;
|
|
1611
|
+
const recordedLanguage = typeof recordedLanguageValue === "string" && recordedLanguageValue ? recordedLanguageValue : void 0;
|
|
1612
|
+
const newlyTyped = requestedType !== void 0 && requestedType !== recordedType;
|
|
1613
|
+
const recordedLanguageFits = !typeLanguages || recordedLanguage !== void 0 && typeLanguages.includes(recordedLanguage);
|
|
1614
|
+
const primaryLanguage = input.primaryLanguage ?? (newlyTyped && !recordedLanguageFits ? typeLanguages?.[0] : void 0) ?? recordedLanguage ?? typeLanguages?.[0] ?? "python";
|
|
1563
1615
|
const candidateProject = {
|
|
1564
1616
|
...existing ?? {},
|
|
1565
1617
|
name: input.name,
|
|
@@ -1581,7 +1633,10 @@ function planProjectInit(input) {
|
|
|
1581
1633
|
commonproject: {
|
|
1582
1634
|
...existing?.template?.commonproject,
|
|
1583
1635
|
enabled: true,
|
|
1584
|
-
primary_language:
|
|
1636
|
+
primary_language: primaryLanguage,
|
|
1637
|
+
// Written only for a non-base type (or one already recorded), so a
|
|
1638
|
+
// base record never gains a key and stays equivalent to its last run.
|
|
1639
|
+
...projectType !== "base" || recordedType !== void 0 ? { project_type: projectType } : {}
|
|
1585
1640
|
}
|
|
1586
1641
|
},
|
|
1587
1642
|
ticket_provider: { ...existing?.ticket_provider, ...buildTicketProviderBlock({
|
|
@@ -1617,6 +1672,7 @@ function planProjectInit(input) {
|
|
|
1617
1672
|
{ kind: "registry.upsert", registryPath, slug, project }
|
|
1618
1673
|
];
|
|
1619
1674
|
if (scaffold) {
|
|
1675
|
+
assertTemplateOffersProjectType(pjanglerRoot, projectType);
|
|
1620
1676
|
actions.push(buildCommonProjectCopierAction({
|
|
1621
1677
|
pjanglerRoot,
|
|
1622
1678
|
targetDir,
|
|
@@ -1630,6 +1686,8 @@ function planProjectInit(input) {
|
|
|
1630
1686
|
boardId: project.ticket_provider.board_id ?? "",
|
|
1631
1687
|
projectIdentifier: identifier,
|
|
1632
1688
|
primaryLanguage: project.template.commonproject.primary_language,
|
|
1689
|
+
projectType,
|
|
1690
|
+
...projectType === "obsidian-plugin" ? { pluginAuthor: operatorDisplayName(input.cwd ?? process.cwd()) } : {},
|
|
1633
1691
|
agentHooksLayer: resolveAgentHooksLayer2(input.agentHooksLayer),
|
|
1634
1692
|
overwrite
|
|
1635
1693
|
}));
|
|
@@ -1768,6 +1826,13 @@ async function executeProjectInitPlan(plan, options = {}) {
|
|
|
1768
1826
|
errors.push(`copier exited with status ${result2.status ?? "unknown"}`);
|
|
1769
1827
|
break;
|
|
1770
1828
|
}
|
|
1829
|
+
const projectType = action.data.project_type ?? "base";
|
|
1830
|
+
const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync7(join12(action.targetDir, rel)));
|
|
1831
|
+
if (missing.length) {
|
|
1832
|
+
errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
|
|
1833
|
+
break;
|
|
1834
|
+
}
|
|
1835
|
+
if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
|
|
1771
1836
|
} else if (action.kind === "project.write-manifest") {
|
|
1772
1837
|
if (action.expectedContent !== void 0 && (!existsSync7(action.path) || readFileSync10(action.path, "utf8") !== action.expectedContent)) {
|
|
1773
1838
|
errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
|
|
@@ -1887,13 +1952,18 @@ function formatProjectInitPlan(plan) {
|
|
|
1887
1952
|
lines.push(` ${cyan(bold(glyph.chevron))} ${title}${plan.dryRun ? ` ${dim(glyph.dot)} ${yellow("dry run")}` : ""}`);
|
|
1888
1953
|
lines.push(` ${dim("registry".padEnd(8))} ${dim(plan.registryPath)}`);
|
|
1889
1954
|
lines.push(` ${dim("target".padEnd(8))} ${dim(plan.project.repo_path)}`);
|
|
1955
|
+
const projectType = plan.project.template?.commonproject?.project_type;
|
|
1956
|
+
if (projectType && projectType !== "base") lines.push(` ${dim("type".padEnd(8))} ${cyan(projectType)}`);
|
|
1890
1957
|
for (const warning of plan.warnings ?? []) lines.push(` ${yellow(glyph.warn)} ${warning}`);
|
|
1891
1958
|
lines.push("");
|
|
1892
1959
|
lines.push(` ${bold("Actions")} ${dim(`(${plan.actions.length})`)}`);
|
|
1893
1960
|
if (!plan.actions.length) lines.push(` ${dim("(nothing to do)")}`);
|
|
1894
1961
|
for (const action of plan.actions) {
|
|
1895
1962
|
lines.push(` ${cyan(glyph.bullet)} ${action.kind}`);
|
|
1896
|
-
if (action.kind === "copier.copy.commonproject")
|
|
1963
|
+
if (action.kind === "copier.copy.commonproject") {
|
|
1964
|
+
lines.push(` ${dim(`target: ${action.targetDir}`)}`);
|
|
1965
|
+
if (action.data.project_type && action.data.project_type !== "base") lines.push(` ${dim(`type: ${action.data.project_type}`)}`);
|
|
1966
|
+
}
|
|
1897
1967
|
if (action.kind === "project.write-manifest") lines.push(` ${dim(`path: ${action.path}`)}`);
|
|
1898
1968
|
if (action.kind === "ticket-provider.create-or-link") {
|
|
1899
1969
|
const target = [action.provider, action.workspace, action.identifier].filter(Boolean).join("/");
|
|
@@ -2014,6 +2084,8 @@ function buildCommonProjectCopierAction(input) {
|
|
|
2014
2084
|
board_id: input.boardId ?? input.planeProjectId ?? "",
|
|
2015
2085
|
project_identifier: input.projectIdentifier,
|
|
2016
2086
|
primary_language: input.primaryLanguage,
|
|
2087
|
+
project_type: input.projectType ?? "base",
|
|
2088
|
+
...input.pluginAuthor !== void 0 ? { plugin_author: input.pluginAuthor } : {},
|
|
2017
2089
|
agent_hooks_layer: input.agentHooksLayer ?? true ? "true" : "false"
|
|
2018
2090
|
};
|
|
2019
2091
|
const command = ["copier", "copy", "--trust", "--vcs-ref=HEAD", templateDir, input.targetDir, "--defaults"];
|
|
@@ -2114,7 +2186,7 @@ function expandHome(path) {
|
|
|
2114
2186
|
function isRecord3(value) {
|
|
2115
2187
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2116
2188
|
}
|
|
2117
|
-
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;
|
|
2189
|
+
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;
|
|
2118
2190
|
var init_project = __esm({
|
|
2119
2191
|
"src/project/index.ts"() {
|
|
2120
2192
|
"use strict";
|
|
@@ -2130,6 +2202,13 @@ var init_project = __esm({
|
|
|
2130
2202
|
TICKET_PROVIDER_ADAPTERS_ENV = "PJ_TICKET_PROVIDER_ADAPTERS";
|
|
2131
2203
|
PROJECT_REGISTRY_SCHEMA_VERSION = 1;
|
|
2132
2204
|
DEFAULT_NEW_PROJECT_STATUS = "active";
|
|
2205
|
+
PROJECT_TYPES = ["base", "obsidian-plugin"];
|
|
2206
|
+
PROJECT_TYPE_LANGUAGES = {
|
|
2207
|
+
"obsidian-plugin": ["typescript", "javascript"]
|
|
2208
|
+
};
|
|
2209
|
+
PROJECT_TYPE_SENTINELS = {
|
|
2210
|
+
"obsidian-plugin": ["manifest.json", join12("src", "main.ts")]
|
|
2211
|
+
};
|
|
2133
2212
|
BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
|
|
2134
2213
|
PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
|
|
2135
2214
|
IDENTIFIER_ASSIGNING_PROVIDERS = ["plane", "linear"];
|
|
@@ -8510,9 +8589,24 @@ function lstatIfPresent(path) {
|
|
|
8510
8589
|
}
|
|
8511
8590
|
}
|
|
8512
8591
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
8592
|
+
if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
|
|
8513
8593
|
const packageJson = join5(repoRoot, "package.json");
|
|
8514
8594
|
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";
|
|
8515
8595
|
}
|
|
8596
|
+
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";
|
|
8597
|
+
function recordedProjectType(ctx) {
|
|
8598
|
+
return record(record(readProjectJson(ctx)?.template).commonproject).project_type;
|
|
8599
|
+
}
|
|
8600
|
+
function isObsidianPluginRepo(ctx, repoRoot) {
|
|
8601
|
+
const recorded = recordedProjectType(ctx);
|
|
8602
|
+
if (recorded !== void 0) return recorded === "obsidian-plugin";
|
|
8603
|
+
const manifest = record(tryParseJson(safeReadText2(join5(repoRoot, "manifest.json"))));
|
|
8604
|
+
return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
|
|
8605
|
+
}
|
|
8606
|
+
function unsupportedVersionTypes(conf, script) {
|
|
8607
|
+
const types = conf.split("\n").map((line) => line.trim().split(/\s+/)[0] ?? "").filter((type) => type && !type.startsWith("#"));
|
|
8608
|
+
return [...new Set(types)].filter((type) => !new RegExp(`(^|[\\s|])${type.replace(/[^a-z0-9-]/gi, "")}(\\|[a-z-]+)*\\)`, "m").test(script));
|
|
8609
|
+
}
|
|
8516
8610
|
var BASE_MISE_PATH_ENTRIES = [".mise/scripts", "agents/hermes/pm"];
|
|
8517
8611
|
function requiredMisePathEntries(_ctx) {
|
|
8518
8612
|
return [...BASE_MISE_PATH_ENTRIES];
|
|
@@ -9170,8 +9264,9 @@ function projectJsonFinding(ctx) {
|
|
|
9170
9264
|
};
|
|
9171
9265
|
}
|
|
9172
9266
|
var BMAD_NPM_PACKAGE = "bmad-method";
|
|
9173
|
-
var BMAD_INSTALLER_VERSION = "6.12.
|
|
9267
|
+
var BMAD_INSTALLER_VERSION = "6.12.1";
|
|
9174
9268
|
var BMAD_TARGET_CHANNEL = "next";
|
|
9269
|
+
var BMAD_STABLE_CHANNEL = "latest";
|
|
9175
9270
|
var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
9176
9271
|
var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
|
|
9177
9272
|
var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
|
|
@@ -9365,10 +9460,17 @@ function runBmadInstall(repoRoot, modules = selectedBmadModules(repoRoot), versi
|
|
|
9365
9460
|
const invocation = bmadInstallerInvocation(version);
|
|
9366
9461
|
const result2 = spawnSync3(invocation.command, [...invocation.prefixArgs, ...bmadInstallerArgs(repoRoot, modules)], { encoding: "utf8" });
|
|
9367
9462
|
if (result2.status !== 0) {
|
|
9368
|
-
|
|
9463
|
+
const stdoutTail = result2.stdout?.trim().split("\n").slice(-12).join("\n");
|
|
9464
|
+
return { ok: false, error: result2.stderr?.trim() || stdoutTail || result2.error?.message || `installer exited ${result2.status}` };
|
|
9369
9465
|
}
|
|
9370
9466
|
return { ok: true };
|
|
9371
9467
|
}
|
|
9468
|
+
function bmadChannelTarget(distTags) {
|
|
9469
|
+
const next = distTags?.[BMAD_TARGET_CHANNEL];
|
|
9470
|
+
const stable = distTags?.[BMAD_STABLE_CHANNEL];
|
|
9471
|
+
if (next && stable && compareBmadVersions(stable, next) > 0) return { version: stable, channel: BMAD_STABLE_CHANNEL };
|
|
9472
|
+
return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
|
|
9473
|
+
}
|
|
9372
9474
|
function readInstalledBmadVersion(repoRoot) {
|
|
9373
9475
|
const raw = safeReadText2(join5(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
9374
9476
|
if (!raw) return void 0;
|
|
@@ -10068,6 +10170,15 @@ function createMiseChecks() {
|
|
|
10068
10170
|
if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
10069
10171
|
if (!existsSync3(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
10070
10172
|
if (!existsSync3(manifestPath)) details.push(".mise/version-files.conf missing");
|
|
10173
|
+
const conf = safeReadText2(manifestPath);
|
|
10174
|
+
const script = safeReadText2(versioningPath);
|
|
10175
|
+
if (conf !== null && script !== null) {
|
|
10176
|
+
const unsupported = unsupportedVersionTypes(conf, script);
|
|
10177
|
+
if (unsupported.length) details.push(`.mise/scripts/versioning.sh cannot version type(s) ${unsupported.join(", ")} named in .mise/version-files.conf`);
|
|
10178
|
+
}
|
|
10179
|
+
if (conf !== null && isObsidianPluginRepo(ctx, ctx.repoRoot) && !/^obsidian\s+manifest\.json\s*$/m.test(conf)) {
|
|
10180
|
+
details.push(".mise/version-files.conf does not version manifest.json/versions.json for this obsidian-plugin project");
|
|
10181
|
+
}
|
|
10071
10182
|
return {
|
|
10072
10183
|
id: "mise.versioning",
|
|
10073
10184
|
title: "managed mise versioning block",
|
|
@@ -10609,6 +10720,15 @@ function strayOpInjectLines(text2) {
|
|
|
10609
10720
|
}
|
|
10610
10721
|
return [...new Set(lines)].sort((a, b) => a - b);
|
|
10611
10722
|
}
|
|
10723
|
+
var COPIER_ANSWERS_HEADER = "# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY";
|
|
10724
|
+
function parseCopierAnswers(text2) {
|
|
10725
|
+
try {
|
|
10726
|
+
const parsed = YAML2.parse(text2);
|
|
10727
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { answers: parsed } : { problem: ".copier-answers.yml is not a YAML mapping" };
|
|
10728
|
+
} catch (error) {
|
|
10729
|
+
return { problem: `.copier-answers.yml is not valid YAML (${errorText(error).split("\n")[0]})` };
|
|
10730
|
+
}
|
|
10731
|
+
}
|
|
10612
10732
|
function createProjectProvenanceChecks() {
|
|
10613
10733
|
return [
|
|
10614
10734
|
{
|
|
@@ -10622,16 +10742,14 @@ function createProjectProvenanceChecks() {
|
|
|
10622
10742
|
if (!text2) {
|
|
10623
10743
|
details.push(".copier-answers.yml missing");
|
|
10624
10744
|
} else {
|
|
10625
|
-
if (!text2.startsWith(
|
|
10626
|
-
|
|
10627
|
-
if (
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
const yamlDesc = descMatch?.[1]?.replace(/\n\s+/g, " ").trim() ?? "";
|
|
10634
|
-
if (yamlDesc !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
10745
|
+
if (!text2.startsWith(COPIER_ANSWERS_HEADER)) details.push("missing Copier overwrite warning header");
|
|
10746
|
+
const parsed = parseCopierAnswers(text2);
|
|
10747
|
+
if ("problem" in parsed) details.push(parsed.problem);
|
|
10748
|
+
else {
|
|
10749
|
+
const { answers } = parsed;
|
|
10750
|
+
if (answers._src_path === void 0) details.push("_src_path missing");
|
|
10751
|
+
if (project?.project_name && String(answers.project_name ?? "") !== String(project.project_name)) details.push("project_name drift between .copier-answers.yml and .project.json");
|
|
10752
|
+
if (project?.project_description && String(answers.project_description ?? "") !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
10635
10753
|
}
|
|
10636
10754
|
}
|
|
10637
10755
|
return {
|
|
@@ -10646,14 +10764,18 @@ function createProjectProvenanceChecks() {
|
|
|
10646
10764
|
migrate: (ctx, finding2) => {
|
|
10647
10765
|
const changedFiles = [];
|
|
10648
10766
|
const project = canonicalProjectJson(ctx);
|
|
10649
|
-
const text2 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
10650
|
-
_src_path: ${join5(ctx.pjanglerRoot, "templates", "commonproject")}
|
|
10651
|
-
project_description: ${String(project.project_description)}
|
|
10652
|
-
project_name: ${String(project.project_name)}
|
|
10653
|
-
ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
10654
|
-
`;
|
|
10655
10767
|
const path = join5(ctx.repoRoot, ".copier-answers.yml");
|
|
10656
|
-
|
|
10768
|
+
const current = safeReadText2(path);
|
|
10769
|
+
const existing = current ? YAML2.parseDocument(current) : void 0;
|
|
10770
|
+
const document = existing && !existing.errors.length && YAML2.isMap(existing.contents) ? existing : new YAML2.Document({});
|
|
10771
|
+
document.set("_src_path", join5(ctx.pjanglerRoot, "templates", "commonproject"));
|
|
10772
|
+
document.set("project_description", String(project.project_description));
|
|
10773
|
+
document.set("project_name", String(project.project_name));
|
|
10774
|
+
document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
|
|
10775
|
+
const body = String(document);
|
|
10776
|
+
const text2 = body.startsWith(COPIER_ANSWERS_HEADER) ? body : `${COPIER_ANSWERS_HEADER}
|
|
10777
|
+
${body}`;
|
|
10778
|
+
if (current !== text2) {
|
|
10657
10779
|
changedFiles.push(path);
|
|
10658
10780
|
if (!ctx.dryRun) writeText(path, text2);
|
|
10659
10781
|
}
|
|
@@ -10885,7 +11007,9 @@ function createBmadChecks() {
|
|
|
10885
11007
|
}
|
|
10886
11008
|
const pinned = ctx.bmadVersionPin?.trim();
|
|
10887
11009
|
const resolved = pinned ? void 0 : resolveBmadDistTags(ctx.homeDir);
|
|
10888
|
-
const
|
|
11010
|
+
const channelTarget = pinned ? void 0 : bmadChannelTarget(resolved?.distTags);
|
|
11011
|
+
const available = pinned ?? channelTarget?.version;
|
|
11012
|
+
const channel = channelTarget?.channel ?? BMAD_TARGET_CHANNEL;
|
|
10889
11013
|
if (!available) {
|
|
10890
11014
|
return {
|
|
10891
11015
|
id: "bmad.version",
|
|
@@ -10896,7 +11020,7 @@ function createBmadChecks() {
|
|
|
10896
11020
|
fixable: false
|
|
10897
11021
|
};
|
|
10898
11022
|
}
|
|
10899
|
-
const targetLabel = pinned ? `pinned ${available}` : `${
|
|
11023
|
+
const targetLabel = pinned ? `pinned ${available}` : `${channel} ${available}`;
|
|
10900
11024
|
const staleNote = resolved?.stale ? ` ${glyph.dot} cached` : "";
|
|
10901
11025
|
const comparison = compareBmadVersions(installed, available);
|
|
10902
11026
|
if (pinned ? comparison === 0 : comparison >= 0) {
|
|
@@ -10917,7 +11041,7 @@ function createBmadChecks() {
|
|
|
10917
11041
|
title: "BMAD version currency",
|
|
10918
11042
|
status: "skip",
|
|
10919
11043
|
summary: `BMAD ${installed} installed; stable currency floor unknown (${BMAD_NPM_PACKAGE} latest unresolved)`,
|
|
10920
|
-
details: [`installed: ${installed}`, `available: ${available} (${BMAD_NPM_PACKAGE}@${
|
|
11044
|
+
details: [`installed: ${installed}`, `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`],
|
|
10921
11045
|
fixable: false
|
|
10922
11046
|
};
|
|
10923
11047
|
}
|
|
@@ -10930,7 +11054,7 @@ function createBmadChecks() {
|
|
|
10930
11054
|
summary: `BMAD ${installed} is at or ahead of stable ${stable ?? "unknown"}; ${targetLabel} available${staleNote}`,
|
|
10931
11055
|
details: [
|
|
10932
11056
|
`installed: ${installed}`,
|
|
10933
|
-
`available: ${available} (${BMAD_NPM_PACKAGE}@${
|
|
11057
|
+
`available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`,
|
|
10934
11058
|
stable ? `stable latest: ${stable}` : "",
|
|
10935
11059
|
"run `pj migrate bmad.version` to take the prerelease"
|
|
10936
11060
|
].filter(Boolean),
|
|
@@ -10944,7 +11068,7 @@ function createBmadChecks() {
|
|
|
10944
11068
|
summary: pinnedMismatch ? `BMAD ${installed} does not match ${targetLabel}` : `BMAD ${installed} is behind stable ${stable} \u2014 upgrade available`,
|
|
10945
11069
|
details: [
|
|
10946
11070
|
`installed: ${installed}`,
|
|
10947
|
-
pinned ? `required transaction pin: ${available}` : `available: ${available} (${BMAD_NPM_PACKAGE}@${
|
|
11071
|
+
pinned ? `required transaction pin: ${available}` : `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`,
|
|
10948
11072
|
!pinned && stable ? `stable latest: ${stable}` : "",
|
|
10949
11073
|
"run `pj migrate bmad.version` to upgrade"
|
|
10950
11074
|
].filter(Boolean),
|
|
@@ -10957,13 +11081,13 @@ function createBmadChecks() {
|
|
|
10957
11081
|
}
|
|
10958
11082
|
{
|
|
10959
11083
|
const current = readInstalledBmadVersion(ctx.repoRoot);
|
|
10960
|
-
const target = ctx.bmadVersionPin?.trim() ?? resolveBmadDistTags(ctx.homeDir)?.distTags?.
|
|
11084
|
+
const target = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
10961
11085
|
if (!current || !target || compareBmadVersions(current, target) === 0) {
|
|
10962
11086
|
return { id: finding2.id, title: finding2.title, status: "noop", summary: "BMAD already current", changedFiles: [], details: [] };
|
|
10963
11087
|
}
|
|
10964
11088
|
}
|
|
10965
11089
|
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
10966
|
-
const available = ctx.bmadVersionPin?.trim() ?? resolveBmadDistTags(ctx.homeDir)?.distTags?.
|
|
11090
|
+
const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
|
|
10967
11091
|
const manifestPath = join5(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
10968
11092
|
const manifestSelection = manifestBmadModules(ctx.repoRoot);
|
|
10969
11093
|
if (manifestSelection.status === "invalid") {
|
|
@@ -17586,18 +17710,18 @@ function reportBoardDelivery(board, slug, skipRequested) {
|
|
|
17586
17710
|
log("");
|
|
17587
17711
|
return skipRequested;
|
|
17588
17712
|
}
|
|
17589
|
-
function projectInitActionLabel(
|
|
17590
|
-
switch (kind) {
|
|
17713
|
+
function projectInitActionLabel(action) {
|
|
17714
|
+
switch (action.kind) {
|
|
17591
17715
|
case "registry.upsert":
|
|
17592
17716
|
return "Refresh the project registry index";
|
|
17593
17717
|
case "copier.copy.commonproject":
|
|
17594
|
-
return "Render CommonProject scaffold";
|
|
17718
|
+
return action.data.project_type && action.data.project_type !== "base" ? `Render CommonProject scaffold (${action.data.project_type})` : "Render CommonProject scaffold";
|
|
17595
17719
|
case "project.write-manifest":
|
|
17596
17720
|
return "Write authoritative repo-local .project.json";
|
|
17597
17721
|
case "ticket-provider.create-or-link":
|
|
17598
17722
|
return "Create/link ticket provider project";
|
|
17599
17723
|
default:
|
|
17600
|
-
return kind;
|
|
17724
|
+
return action.kind;
|
|
17601
17725
|
}
|
|
17602
17726
|
}
|
|
17603
17727
|
function registryNeedsUpsert(plan) {
|
|
@@ -17622,7 +17746,7 @@ function actionNeedsRun(plan, kind, syncMode) {
|
|
|
17622
17746
|
async function selectProjectInitOperations(input) {
|
|
17623
17747
|
const planOperations = input.plan.actions.filter((action) => actionNeedsRun(input.plan, action.kind, input.syncMode)).map((action) => ({
|
|
17624
17748
|
value: action.kind,
|
|
17625
|
-
label: projectInitActionLabel(action
|
|
17749
|
+
label: projectInitActionLabel(action),
|
|
17626
17750
|
hint: action.kind === "registry.upsert" ? input.plan.registryPath : action.kind
|
|
17627
17751
|
}));
|
|
17628
17752
|
const parityOperations = input.auditRules.filter((rule) => rule.fixable && rule.status !== "pass" && rule.status !== "skip").map((rule) => ({
|
|
@@ -17742,7 +17866,7 @@ program.configureOutput({
|
|
|
17742
17866
|
});
|
|
17743
17867
|
registerNotebookCli(program);
|
|
17744
17868
|
program.name("pjangler").description("Project subsystem bootstrapper CLI").version(PJANGLER_VERSION);
|
|
17745
|
-
program.command("init").argument("[name]", "Name of a NEW project; creates ./<name>. Omit to adopt the repo you are standing in.").description("Initialize the project manifest, scaffold and registry index").option("--description <text>", "Project description").option("--target-dir <path>", "Adopt an existing repo at this path (the only way to init a directory you are not standing in)").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language for
|
|
17869
|
+
program.command("init").argument("[name]", "Name of a NEW project; creates ./<name>. Omit to adopt the repo you are standing in.").description("Initialize the project manifest, scaffold and registry index").option("--description <text>", "Project description").option("--target-dir <path>", "Adopt an existing repo at this path (the only way to init a directory you are not standing in)").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language recorded for the project (default: the recorded one, else the project type's, else python)").option("--obsidian-plugin", "Render the CommonProject variant built on the canonical Obsidian sample plugin (new projects; on an existing repo, records one that already is a plugin)").option("--apply", "Write the project manifest, render the scaffold and refresh its index").option("--dry-run", "Preview changes without writing files (default)").option("--live", "Allow host-level external effects (systemd, notebook reconcile). The ticket board is created by default and does not need this.").option("--id <project-id>", "Canonical project ID (case-insensitive)").addOption(new Option("--slug <slug>", "Legacy alias for --id").hideHelp()).option("--identifier <identifier>", "Provider ticket prefix (board metadata)").option("--ticket-provider <type>", "Ticket provider: plane | trello (default: existing binding or plane)").option("--board-id <id>", "Board id (Plane project UUID or Trello board id)").option("--board-url <url>", "Deprecated no-op; board URLs are derived from provider + workspace + board-id").option("--skip-board", "Do not create or link a ticket board (the record stays unlinked, and init says so)").option("--workspace <name>", "Ticket workspace/org (Plane workspace; blank for Trello)").option("--registry <location>", `Registry service URL or fixture path (default: ${projectRegistryPath()})`).option("-f, --force", "Allow replacing an existing registry entry, re-rendering files, and renaming an already-registered project").option("-y, --yes", "Apply every proposed operation without prompting").option("--no-tui", "Disable interactive prompts").option("--json", "Output machine-parseable JSON").action(async (name, options) => {
|
|
17746
17870
|
if (name && getRecipeNames().includes(name)) {
|
|
17747
17871
|
if (!options.json) {
|
|
17748
17872
|
console.error(`${yellow(glyph.warn)} ${dim(`"pjangler init ${name}" is deprecated \u2014 use "pjangler add ${name}". Forwarding\u2026`)}`);
|
|
@@ -17865,7 +17989,7 @@ boardCmd.command("modules").description("List this project's modules").option("-
|
|
|
17865
17989
|
}
|
|
17866
17990
|
});
|
|
17867
17991
|
var projectCmd = program.command("project", { hidden: true }).description("Legacy aliases for project commands");
|
|
17868
|
-
projectCmd.command("init").argument("[name]", "Project display name").description("Plan or apply a registry-backed CommonProject initialization or legacy repo sync").option("--description <text>", "Project description").option("--target-dir <path>", "Adopt an existing repo at this path (the only way to init a directory you are not standing in)").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language for
|
|
17992
|
+
projectCmd.command("init").argument("[name]", "Project display name").description("Plan or apply a registry-backed CommonProject initialization or legacy repo sync").option("--description <text>", "Project description").option("--target-dir <path>", "Adopt an existing repo at this path (the only way to init a directory you are not standing in)").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language recorded for the project (default: the recorded one, else the project type's, else python)").option("--obsidian-plugin", "Render the CommonProject variant built on the canonical Obsidian sample plugin (new projects; on an existing repo, records one that already is a plugin)").option("--apply", "Write the project manifest, render the scaffold and refresh its index").option("--dry-run", "Preview changes without writing files (default)").option("--live", "Allow host-level external effects (systemd, notebook reconcile). The ticket board is created by default and does not need this.").option("--id <project-id>", "Canonical project ID (case-insensitive)").addOption(new Option("--slug <slug>", "Legacy alias for --id").hideHelp()).option("--identifier <identifier>", "Provider ticket prefix (board metadata)").option("--ticket-provider <type>", "Ticket provider: plane | trello (default: existing binding or plane)").option("--board-id <id>", "Board id (Plane project UUID or Trello board id)").option("--board-url <url>", "Deprecated no-op; board URLs are derived from provider + workspace + board-id").option("--skip-board", "Do not create or link a ticket board (the record stays unlinked, and init says so)").option("--workspace <name>", "Ticket workspace/org (Plane workspace; blank for Trello)").option("--registry <location>", `Registry service URL or fixture path (default: ${projectRegistryPath()})`).option("-f, --force", "Allow replacing an existing registry entry, re-rendering files, and renaming an already-registered project").option("-y, --yes", "Apply every proposed operation without prompting").option("--no-tui", "Disable interactive prompts").option("--json", "Output machine-parseable JSON").action((name, options) => {
|
|
17869
17993
|
if (!options.json) console.error(`${yellow(glyph.warn)} ${dim('"pjangler project init" is deprecated \u2014 use "pjangler init".')}`);
|
|
17870
17994
|
return runProjectInit(name, options);
|
|
17871
17995
|
});
|
|
@@ -17880,6 +18004,7 @@ async function runProjectInit(name, options) {
|
|
|
17880
18004
|
targetDir: target.targetDir,
|
|
17881
18005
|
sourceSkill: options.sourceSkill,
|
|
17882
18006
|
primaryLanguage: options.primaryLanguage,
|
|
18007
|
+
projectType: options.obsidianPlugin ? "obsidian-plugin" : void 0,
|
|
17883
18008
|
apply,
|
|
17884
18009
|
live: options.live ?? false,
|
|
17885
18010
|
projectSlug: target.slug,
|
|
@@ -17968,6 +18093,14 @@ async function runProjectInit(name, options) {
|
|
|
17968
18093
|
if (result2.ok && result2.changedFiles.length) console.log(` ${green(glyph.pass)} ${bold("Project synchronized")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
|
|
17969
18094
|
if (result2.ok && result2.changedFiles.length === 0) console.log(` ${green(glyph.pass)} ${dim("Already in parity")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
|
|
17970
18095
|
reportBoardDelivery(board, plan.project.slug, skipRequested);
|
|
18096
|
+
const rendered = selectedPlan.actions.find((action) => action.kind === "copier.copy.commonproject");
|
|
18097
|
+
if (result2.ok && rendered?.kind === "copier.copy.commonproject" && rendered.data.project_type === "obsidian-plugin") {
|
|
18098
|
+
console.log(` ${bold("Next")} ${dim(`cd ${target.targetDir}`)}`);
|
|
18099
|
+
console.log(` ${cyan("npm install")} ${dim("dev dependencies, pinned by the committed package-lock.json")}`);
|
|
18100
|
+
console.log(` ${cyan("npm run dev")} ${dim("watch build -> main.js")}`);
|
|
18101
|
+
console.log(` ${cyan("mise run plugin:link <vault>")} ${dim("load it in an Obsidian vault")}`);
|
|
18102
|
+
console.log("");
|
|
18103
|
+
}
|
|
17971
18104
|
}
|
|
17972
18105
|
process.exitCode = result2.ok && boardDelivered ? 0 : 1;
|
|
17973
18106
|
} catch (err) {
|