@delorenj/pjangler 1.5.50 → 1.5.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1435,6 +1435,11 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
1435
1435
  candidates.push(join18(homedir7(), "code", "33GOD", relativeRoot, file));
1436
1436
  return candidates.find((candidate) => existsSync11(candidate));
1437
1437
  }
1438
+ function providerBoardName(provider, name) {
1439
+ if (provider !== "plane") return name;
1440
+ const cleaned = name.replace(/[^\p{L}\p{N}_\s]+/gu, " ").replace(/\s+/g, " ").trim();
1441
+ return cleaned || name;
1442
+ }
1438
1443
  function provisionTicketProviderBoard(action, env2 = process.env) {
1439
1444
  const provider = action.provider;
1440
1445
  const groups = ticketProviderKeyGroups(provider, action.workspace);
@@ -1775,11 +1780,11 @@ function planProjectInit(input) {
1775
1780
  workspace: project.ticket_provider.workspace ?? "33god",
1776
1781
  identifier,
1777
1782
  repoPath: targetDir,
1778
- boardName: project.name,
1783
+ boardName: providerBoardName(project.ticket_provider.type, project.name),
1779
1784
  description: project.description || `Ticket board for ${project.slug}`,
1780
1785
  boardId: project.ticket_provider.board_id ?? "",
1781
1786
  state: project.ticket_provider.board_id ? "linked" : "planned",
1782
- reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${project.name}" (${identifier}) via the ticket-provider adapter`
1787
+ reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${providerBoardName(project.ticket_provider.type, project.name)}" (${identifier}) via the ticket-provider adapter`
1783
1788
  }
1784
1789
  );
1785
1790
  return {
@@ -6128,6 +6133,15 @@ function isHostScoped(finding2) {
6128
6133
  function gatesProject(finding2) {
6129
6134
  return finding2.status === "fail" && !isHostScoped(finding2);
6130
6135
  }
6136
+ var FINDING_DETAIL_LIMIT = 20;
6137
+ function describeFinding(finding2) {
6138
+ const details = (finding2.details ?? []).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
6139
+ if (details.length === 0) return `${finding2.id}: ${finding2.summary}`;
6140
+ const shown = details.slice(0, FINDING_DETAIL_LIMIT).map((line) => ` - ${line.replace(/\r?\n/g, "\n ")}`);
6141
+ const omitted = details.length - shown.length;
6142
+ if (omitted > 0) shown.push(` - (+${omitted} more; run pj audit --rules ${finding2.id} for the full list)`);
6143
+ return [`${finding2.id}: ${finding2.summary}`, ...shown].join("\n");
6144
+ }
6131
6145
 
6132
6146
  // src/recipes/Recipe.ts
6133
6147
  function commandStatus(result2, dryRun) {
@@ -6186,7 +6200,7 @@ var Recipe = class {
6186
6200
  }
6187
6201
  if (!finding2.fixable) {
6188
6202
  phases.push({ id: check.id, status: "failed", changedFiles: [], message: finding2.summary });
6189
- errors.push(`${check.id}: ${finding2.summary}`);
6203
+ errors.push(describeFinding({ id: check.id, summary: finding2.summary, details: finding2.details }));
6190
6204
  break;
6191
6205
  }
6192
6206
  const migrated = await check.migrate(ctx, finding2);
@@ -6196,7 +6210,7 @@ var Recipe = class {
6196
6210
  logs.push(`${check.id}: ${migrated.summary}`);
6197
6211
  changedFiles.push(...actualChanges);
6198
6212
  if (status === "failed") {
6199
- errors.push(`${check.id}: ${migrated.summary}`);
6213
+ errors.push(describeFinding({ id: check.id, summary: migrated.summary, details: migrated.details }));
6200
6214
  break;
6201
6215
  }
6202
6216
  if (!ctx.dryRun) {
@@ -7636,7 +7650,11 @@ function attestBmadInstallerFiles(inventory, base, files, label) {
7636
7650
 
7637
7651
  // src/parity/skill-roots.ts
7638
7652
  var CANONICAL_CLI_SKILLS_ALIAS = "../.agents/skills";
7639
- var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.map((cli) => cli.skillsRoot);
7653
+ var SKILLEX_ONLY_ALIASES_AFTER = { ".kimi-code/skills": ".kimi/skills" };
7654
+ var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.flatMap((cli) => {
7655
+ const follower = SKILLEX_ONLY_ALIASES_AFTER[cli.skillsRoot];
7656
+ return follower ? [cli.skillsRoot, follower] : [cli.skillsRoot];
7657
+ });
7640
7658
  var SKILL_ROOTS_QUARANTINE = ".pjangler-quarantine";
7641
7659
  function lstatOrUndefined(path) {
7642
7660
  try {
@@ -8812,7 +8830,7 @@ function readHandoffRecord(ctx) {
8812
8830
  }
8813
8831
  function writeHandoffRecord(ctx, names) {
8814
8832
  const path = handoffRecordPath(ctx);
8815
- mkdirSync5(dirname7(path), { recursive: true });
8833
+ mkdirSync5(dirname7(path), { recursive: true, mode: 448 });
8816
8834
  const temporary = `${path}.${process.pid}.tmp`;
8817
8835
  writeFileSync5(temporary, `${JSON.stringify({ project: realpathOf(ctx.repoRoot) ?? resolve5(ctx.repoRoot), excluded: [...new Set(names)].sort() }, null, 2)}
8818
8836
  `);
@@ -17357,12 +17375,12 @@ var ProjectRecipe = class extends Recipe {
17357
17375
  changedFiles: result2.status === "applied" || result2.status === "partial" ? result2.changedFiles : [],
17358
17376
  message: result2.summary
17359
17377
  })));
17360
- errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => `${result2.id}: ${result2.summary}`));
17378
+ errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => describeFinding(result2)));
17361
17379
  }
17362
17380
  const eligibilityAudit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : void 0;
17363
17381
  audit = eligibilityAudit;
17364
17382
  if (eligibilityAudit && !eligibilityAudit.ok) {
17365
- errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => `${finding2.id}: ${finding2.summary}`));
17383
+ errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
17366
17384
  }
17367
17385
  phases.push({
17368
17386
  id: "project.audit:eligibility",
@@ -17453,7 +17471,7 @@ var ProjectRecipe = class extends Recipe {
17453
17471
  transactionContext.notebookManifestBinding = projectedNotebook?.binding ? Object.freeze({ ...projectedNotebook.binding }) : null;
17454
17472
  transactionContext.notebookObservation = Object.freeze(await notebookRecipe.observeExternal(plan, notebookPlan));
17455
17473
  const candidateAudit = publicAudit(await this.registry.auditRecipes(transactionContext, ["notebook"]));
17456
- if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => `${item.id}: ${item.summary}`));
17474
+ if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => describeFinding(item)));
17457
17475
  }
17458
17476
  phases.push({
17459
17477
  id: "project.external:notebook",
@@ -17509,7 +17527,7 @@ var ProjectRecipe = class extends Recipe {
17509
17527
  }
17510
17528
  audit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : audit;
17511
17529
  if (errors.length === 0 && audit && !audit.ok) {
17512
- errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => `${finding2.id}: ${finding2.summary}`));
17530
+ errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
17513
17531
  }
17514
17532
  phases.push({
17515
17533
  id: "project.audit",
@@ -23120,11 +23138,19 @@ async function promptTextValue(message2, initialValue) {
23120
23138
  }
23121
23139
  return value.trim();
23122
23140
  }
23123
- function reportBoardDelivery(board, slug, skipRequested) {
23141
+ function reportBoardDelivery(board, slug, skipRequested, reached = true) {
23124
23142
  if (board.confirmed) {
23125
23143
  console.log(` ${green(glyph.pass)} ${bold("Board linked")} ${dim(glyph.dot)} ${cyan(`${board.provider}/${board.workspace}/${board.identifier}`)} ${dim(board.boardId)}`);
23126
23144
  return true;
23127
23145
  }
23146
+ if (!reached && !skipRequested) {
23147
+ console.error("");
23148
+ console.error(` ${xmark2} ${bold("Board step not reached")} ${dim(glyph.dot)} ${cyan(slug)}`);
23149
+ console.error(` ${dim("project init failed before the ticket-provider phase; no board was created or looked up")}`);
23150
+ console.error(` ${dim("fix the error(s) above, then re-run the same command. The provider credential is not the cause.")}`);
23151
+ console.error("");
23152
+ return false;
23153
+ }
23128
23154
  const mark = skipRequested ? `${yellow(glyph.warn)}` : xmark2;
23129
23155
  const title = skipRequested ? "No ticket board (--skip-board)" : "No ticket board";
23130
23156
  const log = skipRequested ? console.log : console.error;
@@ -23516,8 +23542,9 @@ async function runProjectInit(name, options) {
23516
23542
  const board = boardDelivery({ ...result2.plan, actions: plan.actions });
23517
23543
  const skipRequested = Boolean(options.skipBoard);
23518
23544
  const boardDelivered = board.confirmed || skipRequested;
23545
+ const boardStepReached = result2.phases.some((phase) => phase.id === "project.external:ticket-provider");
23519
23546
  if (options.json) {
23520
- console.log(JSON.stringify({ ...result2, ok: result2.ok && boardDelivered, board }, null, 2));
23547
+ console.log(JSON.stringify({ ...result2, ok: result2.ok && boardDelivered, board: { ...board, reached: boardStepReached } }, null, 2));
23521
23548
  } else {
23522
23549
  console.log(formatProjectInitPlan(selectedPlan));
23523
23550
  for (const line of result2.logs) console.log(line);
@@ -23525,7 +23552,7 @@ async function runProjectInit(name, options) {
23525
23552
  if (result2.migrationReport) console.log(formatMigrationReport(result2.migrationReport));
23526
23553
  if (result2.ok && result2.changedFiles.length) console.log(` ${green(glyph.pass)} ${bold("Project synchronized")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
23527
23554
  if (result2.ok && result2.changedFiles.length === 0) console.log(` ${green(glyph.pass)} ${dim("Already in parity")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
23528
- reportBoardDelivery(board, plan.project.slug, skipRequested);
23555
+ reportBoardDelivery(board, plan.project.slug, skipRequested, boardStepReached || result2.ok);
23529
23556
  const rendered = selectedPlan.actions.find((action) => action.kind === "copier.copy.commonproject");
23530
23557
  if (result2.ok && rendered?.kind === "copier.copy.commonproject" && rendered.data.project_type === "obsidian-plugin") {
23531
23558
  console.log(` ${bold("Next")} ${dim(`cd ${target.targetDir}`)}`);
@@ -1295,6 +1295,11 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
1295
1295
  candidates.push(join17(homedir5(), "code", "33GOD", relativeRoot, file));
1296
1296
  return candidates.find((candidate) => existsSync11(candidate));
1297
1297
  }
1298
+ function providerBoardName(provider, name) {
1299
+ if (provider !== "plane") return name;
1300
+ const cleaned = name.replace(/[^\p{L}\p{N}_\s]+/gu, " ").replace(/\s+/g, " ").trim();
1301
+ return cleaned || name;
1302
+ }
1298
1303
  function provisionTicketProviderBoard(action, env2 = process.env) {
1299
1304
  const provider = action.provider;
1300
1305
  const groups = ticketProviderKeyGroups(provider, action.workspace);
@@ -1646,11 +1651,11 @@ function planProjectInit(input) {
1646
1651
  workspace: project.ticket_provider.workspace ?? "33god",
1647
1652
  identifier,
1648
1653
  repoPath: targetDir,
1649
- boardName: project.name,
1654
+ boardName: providerBoardName(project.ticket_provider.type, project.name),
1650
1655
  description: project.description || `Ticket board for ${project.slug}`,
1651
1656
  boardId: project.ticket_provider.board_id ?? "",
1652
1657
  state: project.ticket_provider.board_id ? "linked" : "planned",
1653
- reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${project.name}" (${identifier}) via the ticket-provider adapter`
1658
+ reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${providerBoardName(project.ticket_provider.type, project.name)}" (${identifier}) via the ticket-provider adapter`
1654
1659
  }
1655
1660
  );
1656
1661
  return {
@@ -5360,6 +5365,15 @@ function isHostScoped(finding2) {
5360
5365
  function gatesProject(finding2) {
5361
5366
  return finding2.status === "fail" && !isHostScoped(finding2);
5362
5367
  }
5368
+ var FINDING_DETAIL_LIMIT = 20;
5369
+ function describeFinding(finding2) {
5370
+ const details = (finding2.details ?? []).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
5371
+ if (details.length === 0) return `${finding2.id}: ${finding2.summary}`;
5372
+ const shown = details.slice(0, FINDING_DETAIL_LIMIT).map((line) => ` - ${line.replace(/\r?\n/g, "\n ")}`);
5373
+ const omitted = details.length - shown.length;
5374
+ if (omitted > 0) shown.push(` - (+${omitted} more; run pj audit --rules ${finding2.id} for the full list)`);
5375
+ return [`${finding2.id}: ${finding2.summary}`, ...shown].join("\n");
5376
+ }
5363
5377
 
5364
5378
  // src/recipes/Recipe.ts
5365
5379
  function commandStatus(result2, dryRun) {
@@ -5418,7 +5432,7 @@ var Recipe = class {
5418
5432
  }
5419
5433
  if (!finding2.fixable) {
5420
5434
  phases.push({ id: check.id, status: "failed", changedFiles: [], message: finding2.summary });
5421
- errors.push(`${check.id}: ${finding2.summary}`);
5435
+ errors.push(describeFinding({ id: check.id, summary: finding2.summary, details: finding2.details }));
5422
5436
  break;
5423
5437
  }
5424
5438
  const migrated = await check.migrate(ctx, finding2);
@@ -5428,7 +5442,7 @@ var Recipe = class {
5428
5442
  logs.push(`${check.id}: ${migrated.summary}`);
5429
5443
  changedFiles.push(...actualChanges);
5430
5444
  if (status === "failed") {
5431
- errors.push(`${check.id}: ${migrated.summary}`);
5445
+ errors.push(describeFinding({ id: check.id, summary: migrated.summary, details: migrated.details }));
5432
5446
  break;
5433
5447
  }
5434
5448
  if (!ctx.dryRun) {
@@ -6868,7 +6882,11 @@ function attestBmadInstallerFiles(inventory, base, files, label) {
6868
6882
 
6869
6883
  // src/parity/skill-roots.ts
6870
6884
  var CANONICAL_CLI_SKILLS_ALIAS = "../.agents/skills";
6871
- var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.map((cli) => cli.skillsRoot);
6885
+ var SKILLEX_ONLY_ALIASES_AFTER = { ".kimi-code/skills": ".kimi/skills" };
6886
+ var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.flatMap((cli) => {
6887
+ const follower = SKILLEX_ONLY_ALIASES_AFTER[cli.skillsRoot];
6888
+ return follower ? [cli.skillsRoot, follower] : [cli.skillsRoot];
6889
+ });
6872
6890
  var SKILL_ROOTS_QUARANTINE = ".pjangler-quarantine";
6873
6891
  function lstatOrUndefined(path) {
6874
6892
  try {
@@ -7975,7 +7993,7 @@ function readHandoffRecord(ctx) {
7975
7993
  }
7976
7994
  function writeHandoffRecord(ctx, names) {
7977
7995
  const path = handoffRecordPath(ctx);
7978
- mkdirSync5(dirname6(path), { recursive: true });
7996
+ mkdirSync5(dirname6(path), { recursive: true, mode: 448 });
7979
7997
  const temporary = `${path}.${process.pid}.tmp`;
7980
7998
  writeFileSync5(temporary, `${JSON.stringify({ project: realpathOf(ctx.repoRoot) ?? resolve4(ctx.repoRoot), excluded: [...new Set(names)].sort() }, null, 2)}
7981
7999
  `);
@@ -15851,12 +15869,12 @@ var ProjectRecipe = class extends Recipe {
15851
15869
  changedFiles: result2.status === "applied" || result2.status === "partial" ? result2.changedFiles : [],
15852
15870
  message: result2.summary
15853
15871
  })));
15854
- errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => `${result2.id}: ${result2.summary}`));
15872
+ errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => describeFinding(result2)));
15855
15873
  }
15856
15874
  const eligibilityAudit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : void 0;
15857
15875
  audit = eligibilityAudit;
15858
15876
  if (eligibilityAudit && !eligibilityAudit.ok) {
15859
- errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => `${finding2.id}: ${finding2.summary}`));
15877
+ errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
15860
15878
  }
15861
15879
  phases.push({
15862
15880
  id: "project.audit:eligibility",
@@ -15947,7 +15965,7 @@ var ProjectRecipe = class extends Recipe {
15947
15965
  transactionContext.notebookManifestBinding = projectedNotebook?.binding ? Object.freeze({ ...projectedNotebook.binding }) : null;
15948
15966
  transactionContext.notebookObservation = Object.freeze(await notebookRecipe.observeExternal(plan, notebookPlan));
15949
15967
  const candidateAudit = publicAudit(await this.registry.auditRecipes(transactionContext, ["notebook"]));
15950
- if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => `${item.id}: ${item.summary}`));
15968
+ if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => describeFinding(item)));
15951
15969
  }
15952
15970
  phases.push({
15953
15971
  id: "project.external:notebook",
@@ -16003,7 +16021,7 @@ var ProjectRecipe = class extends Recipe {
16003
16021
  }
16004
16022
  audit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : audit;
16005
16023
  if (errors.length === 0 && audit && !audit.ok) {
16006
- errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => `${finding2.id}: ${finding2.summary}`));
16024
+ errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
16007
16025
  }
16008
16026
  phases.push({
16009
16027
  id: "project.audit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delorenj/pjangler",
3
- "version": "1.5.50",
3
+ "version": "1.5.51",
4
4
  "description": "Project subsystem bootstrapper CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -61,7 +61,7 @@
61
61
  "license": "MIT",
62
62
  "dependencies": {
63
63
  "@clack/prompts": "^1.4.0",
64
- "@delorenj/skillex": "0.3.0",
64
+ "@delorenj/skillex": "0.4.4",
65
65
  "@modelcontextprotocol/sdk": "^1.29.0",
66
66
  "commander": "^14.0.2",
67
67
  "ink": "^8.0.0",