@pareto4/pp-core 1.7.0 → 1.8.0
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/.claude-plugin/plugin.json +1 -1
- package/bin/install.js +116 -0
- package/commands/pp-build-handoff.md +46 -0
- package/commands/pp-discovery-to-build.md +40 -0
- package/gemini-extension.json +1 -1
- package/package.json +2 -1
- package/pareto-skills/_shared/create-project-board.sh +195 -0
- package/pareto-skills/_shared/pp-claude-md-protocol.md +306 -0
- package/pareto-skills/_shared/sync-phase-status.sh +139 -0
- package/pareto-skills/pp-brand/SKILL.md +644 -0
- package/pareto-skills/pp-brand/assets/crew/ParetoAgent.inline.html +120 -0
- package/pareto-skills/pp-brand/assets/crew/ParetoAgent.tsx +239 -0
- package/pareto-skills/pp-brand/assets/crew/build.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/deploy.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/discovery.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/plan.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/run.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/validate.svg +11 -0
- package/pareto-skills/pp-brand/assets/pareto-brand-preview.html +547 -0
- package/pareto-skills/pp-obsidian-notes/SKILL.md +127 -0
- package/pareto-skills/pp-obsidian-notes/templates/CLAUDE.md +29 -0
- package/pareto-skills/pp-obsidian-notes/templates/client-context.md +116 -0
- package/pareto-skills/pp-obsidian-notes/templates/meeting-note.md +66 -0
- package/pareto-skills/pp-obsidian-notes/templates/pareto-context.md +102 -0
- package/pareto-skills/pp-obsidian-notes/templates/proposal.md +117 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1364 -0
- package/pp-core/bin/lib/intel.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +120 -0
- package/pp-core/bin/lib/profile-pipeline-command-router.cjs +1 -1
- package/pp-core/bin/lib/roadmap-parser.cjs +1 -1
- package/pp-core/bin/lib/runtime-artifact-conversion.cjs +2 -2
- package/pp-core/bin/lib/surface.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +2 -2
- package/pp-core/bin/pp-tools.cjs +23 -0
- package/pp-core/references/ui-brand.md +73 -11
- package/pp-core/templates/codebase/structure.md +5 -5
- package/pp-core/templates/spec.md +7 -7
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/autonomous.md +4 -4
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/discovery-to-build.md +598 -0
- package/pp-core/workflows/execute-phase.md +9 -1
- package/pp-core/workflows/plan-phase.md +14 -12
- package/pp-core/workflows/pr-branch.md +5 -4
- package/pp-core/workflows/reapply-patches.md +1 -1
- package/pp-core/workflows/ship.md +1 -1
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/verify-work.md +6 -5
package/bin/install.js
CHANGED
|
@@ -7387,6 +7387,120 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
|
|
|
7387
7387
|
const nestedGsdDirForCleanup = path.join(configDir, 'skills', 'gsd');
|
|
7388
7388
|
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
|
7389
7389
|
}
|
|
7390
|
+
|
|
7391
|
+
// Verbatim Pareto skill payload — MUST be the LAST step (post-prune ordering
|
|
7392
|
+
// is load-bearing, Pitfall 1). The skills-kind loop above runs _removeGsdEntries,
|
|
7393
|
+
// which unconditionally deletes EVERY pp-* skill dir; running the verbatim copy
|
|
7394
|
+
// here (delete-then-recopy) makes the payload self-healing across repeated
|
|
7395
|
+
// `npx` installs. Copies are byte-for-byte with NO content-rewrite pass (D-03).
|
|
7396
|
+
installVerbatimSkills(runtime, configDir, scope);
|
|
7397
|
+
}
|
|
7398
|
+
|
|
7399
|
+
/**
|
|
7400
|
+
* Locate the bundled `pareto-skills/` source directory.
|
|
7401
|
+
*
|
|
7402
|
+
* Walks up from `__dirname` (up to 6 ancestors) looking for the sentinel
|
|
7403
|
+
* `pareto-skills/pp-brand/SKILL.md`, mirroring the source-root walk-up the
|
|
7404
|
+
* installer already uses to anchor `commands/`. Returns the absolute path to
|
|
7405
|
+
* the `pareto-skills/` directory. Throws a clear error if it cannot be found.
|
|
7406
|
+
*
|
|
7407
|
+
* @returns {string} absolute path to the bundled `pareto-skills/` directory
|
|
7408
|
+
*/
|
|
7409
|
+
function findParetoSkillsSrc() {
|
|
7410
|
+
let dir = __dirname;
|
|
7411
|
+
for (let i = 0; i < 6; i += 1) {
|
|
7412
|
+
const candidate = path.join(dir, 'pareto-skills');
|
|
7413
|
+
if (fs.existsSync(path.join(candidate, 'pp-brand', 'SKILL.md'))) return candidate;
|
|
7414
|
+
const parent = path.dirname(dir);
|
|
7415
|
+
if (parent === dir) break;
|
|
7416
|
+
dir = parent;
|
|
7417
|
+
}
|
|
7418
|
+
throw new Error(`findParetoSkillsSrc: could not locate pareto-skills/ from ${__dirname}`);
|
|
7419
|
+
}
|
|
7420
|
+
|
|
7421
|
+
/**
|
|
7422
|
+
* Place the verbatim Pareto skill payload into a runtime's resolved skills dir.
|
|
7423
|
+
*
|
|
7424
|
+
* This is the "second channel" for shipping skills: unlike command-derived
|
|
7425
|
+
* skills (generated from commands/pp-*.md via the converter), these three
|
|
7426
|
+
* `pp-*` skill dirs — plus a peer `_shared/` — are copied byte-for-byte with
|
|
7427
|
+
* NO content-rewrite pass (D-03/D-07). That keeps them out of docs/COMMANDS.md
|
|
7428
|
+
* and prevents the rewrite walker from corrupting templates or the protocol
|
|
7429
|
+
* file (Pitfall 3). The payload is binary-safe (Crew assets), so we use a plain
|
|
7430
|
+
* recursive fs.cpSync — never applyRuntimeContentRewritesInPlace,
|
|
7431
|
+
* stageSkillsForRuntimeAsSkills, or the skillsKind converter.
|
|
7432
|
+
*
|
|
7433
|
+
* The destination is resolved from the artifact layout's skills-kind
|
|
7434
|
+
* destSubpath (never a hardcoded `skills/`), so Hermes' nested `skills/gsd/`
|
|
7435
|
+
* base is honoured automatically. Runtimes with no skills kind (gemini has only
|
|
7436
|
+
* a commands kind; grok has no skills-layout descriptor) are skipped gracefully
|
|
7437
|
+
* with an early `return 0` — never a throw (Pitfall 5).
|
|
7438
|
+
*
|
|
7439
|
+
* The skill names are a FIXED, package-owned allowlist — never interpolated
|
|
7440
|
+
* input (V5 / T-16-03). No manifest entry is added, so `/pp-surface` pruning
|
|
7441
|
+
* leaves these non-manifest pp-* dirs untouched (Pitfall 2). `_shared/*.sh`
|
|
7442
|
+
* regain their exec bit after copy, because a content-level cpSync does not
|
|
7443
|
+
* reliably preserve the exec bit cross-platform.
|
|
7444
|
+
*
|
|
7445
|
+
* @param {string} runtime - target runtime id
|
|
7446
|
+
* @param {string} configDir - resolved runtime config directory
|
|
7447
|
+
* @param {string} scope - 'local' | 'global'
|
|
7448
|
+
* @returns {number} number of skill dirs placed (0 if the runtime has no skills kind)
|
|
7449
|
+
*/
|
|
7450
|
+
function installVerbatimSkills(runtime, configDir, scope) {
|
|
7451
|
+
// Resolve the runtime's artifact layout. Two graceful-skip cases (Pitfall 5,
|
|
7452
|
+
// no throw): gemini's layout has only a commands kind (skillsKindEntry
|
|
7453
|
+
// undefined below); grok is not in the layout registry at all, so
|
|
7454
|
+
// resolveRuntimeArtifactLayout throws 'Unknown runtime' — treat that as
|
|
7455
|
+
// "no skills surface" and skip. In the production call path
|
|
7456
|
+
// installRuntimeArtifacts has already resolved the layout, but this function
|
|
7457
|
+
// is exported and called standalone by test harnesses (Plan 16-03), so the
|
|
7458
|
+
// guard lives here too.
|
|
7459
|
+
let layout;
|
|
7460
|
+
try {
|
|
7461
|
+
layout = resolveRuntimeArtifactLayout(runtime, configDir, scope);
|
|
7462
|
+
} catch {
|
|
7463
|
+
return 0;
|
|
7464
|
+
}
|
|
7465
|
+
const skillsKindEntry = layout.kinds.find((k) => k.kind === 'skills');
|
|
7466
|
+
// gemini (commands-only): no skills surface — skip, don't throw.
|
|
7467
|
+
if (!skillsKindEntry) return 0;
|
|
7468
|
+
|
|
7469
|
+
const src = findParetoSkillsSrc();
|
|
7470
|
+
// destSubpath already encodes Hermes' nested `skills/gsd` — never hardcode `skills/`.
|
|
7471
|
+
const skillsBase = path.join(configDir, skillsKindEntry.destSubpath);
|
|
7472
|
+
fs.mkdirSync(skillsBase, { recursive: true });
|
|
7473
|
+
|
|
7474
|
+
// FIXED, package-owned allowlist — never interpolated input (V5 / T-16-03).
|
|
7475
|
+
const BUNDLED = ['pp-brand', 'pp-project-setup', 'pp-obsidian-notes'];
|
|
7476
|
+
let placed = 0;
|
|
7477
|
+
for (const name of BUNDLED) {
|
|
7478
|
+
const skillDest = path.join(skillsBase, name);
|
|
7479
|
+
// Plain recursive, binary-safe copy — NO content-rewrite pass (D-03/Pitfall 3).
|
|
7480
|
+
fs.cpSync(path.join(src, name), skillDest, { recursive: true });
|
|
7481
|
+
if (verifyInstalled(skillDest, `skill ${name}`)) {
|
|
7482
|
+
placed += 1;
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
|
|
7486
|
+
// Copy _shared/ as a PEER of the skill dirs so `../_shared/` resolves from any
|
|
7487
|
+
// skill body (D-04). Verbatim copy, no rewrite pass.
|
|
7488
|
+
const sharedDest = path.join(skillsBase, '_shared');
|
|
7489
|
+
fs.cpSync(path.join(src, '_shared'), sharedDest, { recursive: true });
|
|
7490
|
+
verifyInstalled(sharedDest, 'skill _shared');
|
|
7491
|
+
|
|
7492
|
+
// Restore the exec bit on bundled *.sh — cpSync content copy does not reliably
|
|
7493
|
+
// preserve it cross-platform (PATTERNS "No Analog Found").
|
|
7494
|
+
try {
|
|
7495
|
+
for (const entry of fs.readdirSync(sharedDest)) {
|
|
7496
|
+
if (entry.endsWith('.sh')) {
|
|
7497
|
+
fs.chmodSync(path.join(sharedDest, entry), 0o755);
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
} catch { /* best-effort: non-fatal if _shared has no scripts */ }
|
|
7501
|
+
|
|
7502
|
+
console.log(` ${green}✓${reset} Placed ${placed} verbatim Pareto skill(s) + _shared into ${skillsKindEntry.destSubpath}`);
|
|
7503
|
+
return placed;
|
|
7390
7504
|
}
|
|
7391
7505
|
|
|
7392
7506
|
/**
|
|
@@ -12309,6 +12423,8 @@ module.exports = {
|
|
|
12309
12423
|
readGsdCommandNames,
|
|
12310
12424
|
installRuntimeArtifacts,
|
|
12311
12425
|
installOpencodeFamilySkills,
|
|
12426
|
+
installVerbatimSkills,
|
|
12427
|
+
findParetoSkillsSrc,
|
|
12312
12428
|
uninstallRuntimeArtifacts,
|
|
12313
12429
|
parseConfigDirFromArgs,
|
|
12314
12430
|
cleanupLegacyGsdCc,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pp:build-handoff
|
|
3
|
+
description: "[DEPRECATED] Alias for /pp-discovery-to-build — transition a completed Pareto Discovery Sprint into a pp-core-ready project. Use /pp-discovery-to-build; this alias will be removed in a future major."
|
|
4
|
+
argument-hint: "[--skip-validation] [--scaffold]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
- Agent
|
|
13
|
+
- AskUserQuestion
|
|
14
|
+
requires: [new-project]
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<runtime_note>
|
|
18
|
+
**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`. They are equivalent — `vscode_askquestions` is the VS Code Copilot implementation of the same interactive question API.
|
|
19
|
+
</runtime_note>
|
|
20
|
+
|
|
21
|
+
<deprecation_note>
|
|
22
|
+
**Deprecated:** `/pp-build-handoff` was renamed to `/pp-discovery-to-build` in v1.8.0. This
|
|
23
|
+
alias runs the identical workflow and is kept for back-compat with v1.7.x. Prefer
|
|
24
|
+
`/pp-discovery-to-build`; this alias will be removed in a future major release.
|
|
25
|
+
</deprecation_note>
|
|
26
|
+
|
|
27
|
+
<objective>
|
|
28
|
+
Bridge the gap between a completed Pareto Discovery Sprint and pp-core execution.
|
|
29
|
+
|
|
30
|
+
Validates all 13 Discovery sessions are complete, synthesizes their outputs into a
|
|
31
|
+
single structured handoff document, bootstraps the `.planning/` project state
|
|
32
|
+
(PROJECT.md, REQUIREMENTS.md, ROADMAP.md, STATE.md, config.json), and wires up
|
|
33
|
+
cross-references so pp-core agents can trace every decision back to the Discovery
|
|
34
|
+
session that produced it.
|
|
35
|
+
|
|
36
|
+
**After this command:** pp-core is initialized. Run `/pp-plan-phase 1` to start
|
|
37
|
+
execution, or `/pp-autonomous` to run all phases.
|
|
38
|
+
</objective>
|
|
39
|
+
|
|
40
|
+
<execution_context>
|
|
41
|
+
@~/.claude/pp-core/workflows/discovery-to-build.md
|
|
42
|
+
</execution_context>
|
|
43
|
+
|
|
44
|
+
<process>
|
|
45
|
+
Execute end-to-end.
|
|
46
|
+
</process>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pp:discovery-to-build
|
|
3
|
+
description: Transition a completed Pareto Discovery Sprint into a pp-core-ready project — synthesize sessions into .planning/ and wire cross-references.
|
|
4
|
+
argument-hint: "[--skip-validation] [--scaffold]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
- Agent
|
|
13
|
+
- AskUserQuestion
|
|
14
|
+
requires: [new-project]
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<runtime_note>
|
|
18
|
+
**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`. They are equivalent — `vscode_askquestions` is the VS Code Copilot implementation of the same interactive question API.
|
|
19
|
+
</runtime_note>
|
|
20
|
+
|
|
21
|
+
<objective>
|
|
22
|
+
Bridge the gap between a completed Pareto Discovery Sprint and pp-core execution.
|
|
23
|
+
|
|
24
|
+
Validates all 13 Discovery sessions are complete, synthesizes their outputs into a
|
|
25
|
+
single structured handoff document, bootstraps the `.planning/` project state
|
|
26
|
+
(PROJECT.md, REQUIREMENTS.md, ROADMAP.md, STATE.md, config.json), and wires up
|
|
27
|
+
cross-references so pp-core agents can trace every decision back to the Discovery
|
|
28
|
+
session that produced it.
|
|
29
|
+
|
|
30
|
+
**After this command:** pp-core is initialized. Run `/pp-plan-phase 1` to start
|
|
31
|
+
execution, or `/pp-autonomous` to run all phases.
|
|
32
|
+
</objective>
|
|
33
|
+
|
|
34
|
+
<execution_context>
|
|
35
|
+
@~/.claude/pp-core/workflows/discovery-to-build.md
|
|
36
|
+
</execution_context>
|
|
37
|
+
|
|
38
|
+
<process>
|
|
39
|
+
Execute end-to-end.
|
|
40
|
+
</process>
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pp-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "PP Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads pp-core's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pareto4/pp-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Pareto pp-core — The Pareto Project's AI-assisted engineering framework: meta-prompting, context engineering, and spec-driven development for AI coding agents.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pp-core": "bin/install.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"pp-core",
|
|
14
14
|
"assets",
|
|
15
15
|
"agents",
|
|
16
|
+
"pareto-skills",
|
|
16
17
|
".claude-plugin",
|
|
17
18
|
"gemini-extension.json",
|
|
18
19
|
"GEMINI.md",
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# create-project-board.sh
|
|
3
|
+
#
|
|
4
|
+
# Canonical GitHub Project v2 board creation for Pareto skills.
|
|
5
|
+
# Creates (or updates) a project board for a given owner/repo with the
|
|
6
|
+
# standard Pareto status columns, custom fields, and label palette.
|
|
7
|
+
#
|
|
8
|
+
# Used by:
|
|
9
|
+
# - pareto-project-setup (initial scaffold)
|
|
10
|
+
# - pareto-build-handoff (Discovery → pp-core handoff)
|
|
11
|
+
# - pareto-push-issues (standalone requirements push)
|
|
12
|
+
#
|
|
13
|
+
# Single source of truth — edit here, not in the skills.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# source _shared/create-project-board.sh
|
|
17
|
+
# create_project_board "<owner>" "<project_title>" "<repo_slug>"
|
|
18
|
+
#
|
|
19
|
+
# Example:
|
|
20
|
+
# create_project_board "pareto-project" "my-app" "pareto-project/my-app"
|
|
21
|
+
#
|
|
22
|
+
# Environment variables honored:
|
|
23
|
+
# BOARD_OWNER_FALLBACK (default: "@me") — used if org create fails
|
|
24
|
+
#
|
|
25
|
+
# Returns (via stdout, space-separated): <board_number> <owner>
|
|
26
|
+
# Exits non-zero on fatal error.
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
# Canonical column schema
|
|
32
|
+
# -----------------------------------------------------------------------------
|
|
33
|
+
# Any change here propagates to every skill that sources this file.
|
|
34
|
+
#
|
|
35
|
+
# Status flow:
|
|
36
|
+
# Backlog → Discuss → Planned → In Progress → Verification → PR Raised → Done
|
|
37
|
+
#
|
|
38
|
+
# Each column maps 1:1 to a pp-core workflow step:
|
|
39
|
+
# Discuss = /pp-discuss-phase (CONTEXT.md written)
|
|
40
|
+
# Planned = /pp-plan-phase (PLAN.md created)
|
|
41
|
+
# In Progress = /pp-execute-phase (branch active, executing)
|
|
42
|
+
# Verification = VERIFICATION.md written, tests passing
|
|
43
|
+
# PR Raised = PR created from phase branch, awaiting merge
|
|
44
|
+
# Done = PR merged, issue closed
|
|
45
|
+
#
|
|
46
|
+
PARETO_STATUS_OPTIONS='[
|
|
47
|
+
{name: "Backlog", color: GRAY, description: "Not yet started"},
|
|
48
|
+
{name: "Discuss", color: PINK, description: "Context gathering via /pp-discuss-phase"},
|
|
49
|
+
{name: "Planned", color: BLUE, description: "Plans created via /pp-plan-phase"},
|
|
50
|
+
{name: "In Progress", color: YELLOW, description: "Executing via /pp-execute-phase"},
|
|
51
|
+
{name: "Verification", color: ORANGE, description: "Verification passed — VERIFICATION.md written"},
|
|
52
|
+
{name: "PR Raised", color: PURPLE, description: "PR created from phase branch, awaiting merge"},
|
|
53
|
+
{name: "Done", color: GREEN, description: "PR merged, issue closed"}
|
|
54
|
+
]'
|
|
55
|
+
|
|
56
|
+
# Label color palette for per-EPIC labels. Cycle through these in order.
|
|
57
|
+
PARETO_EPIC_COLORS=(
|
|
58
|
+
"0E8A16" "1AA393" "D93F0B" "B44EFF"
|
|
59
|
+
"4A9EFF" "EF4444" "F5C842" "8B5CF6"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# -----------------------------------------------------------------------------
|
|
63
|
+
# create_project_board <owner> <title> <owner/repo>
|
|
64
|
+
# -----------------------------------------------------------------------------
|
|
65
|
+
create_project_board() {
|
|
66
|
+
local OWNER="$1"
|
|
67
|
+
local TITLE="$2"
|
|
68
|
+
local REPO="$3"
|
|
69
|
+
local BOARD_NUM
|
|
70
|
+
|
|
71
|
+
# 1. Check for existing board matching title
|
|
72
|
+
BOARD_NUM=$(gh project list --owner "$OWNER" --format json 2>/dev/null \
|
|
73
|
+
| jq -r ".projects[] | select(.title==\"$TITLE\") | .number" || true)
|
|
74
|
+
|
|
75
|
+
if [ -z "$BOARD_NUM" ]; then
|
|
76
|
+
# 2. Try to create under the given owner
|
|
77
|
+
BOARD_NUM=$(gh project create --owner "$OWNER" --title "$TITLE" \
|
|
78
|
+
--format json 2>/dev/null | jq -r '.number' || true)
|
|
79
|
+
|
|
80
|
+
# 3. Fall back to personal projects if org creation failed
|
|
81
|
+
if [ -z "$BOARD_NUM" ]; then
|
|
82
|
+
OWNER="${BOARD_OWNER_FALLBACK:-@me}"
|
|
83
|
+
BOARD_NUM=$(gh project create --owner "$OWNER" --title "$TITLE" \
|
|
84
|
+
--format json | jq -r '.number')
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# 4. Link board to repo
|
|
88
|
+
gh project link "$BOARD_NUM" --owner "$OWNER" --repo "$REPO" >/dev/null
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# 5. Configure Status field with Pareto schema (idempotent — safe to re-run)
|
|
92
|
+
configure_status_field "$BOARD_NUM" "$OWNER"
|
|
93
|
+
|
|
94
|
+
# 6. Ensure custom fields exist (Priority, Sprint, Estimate, Phase)
|
|
95
|
+
ensure_custom_fields "$BOARD_NUM" "$OWNER"
|
|
96
|
+
|
|
97
|
+
echo "$BOARD_NUM $OWNER"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# -----------------------------------------------------------------------------
|
|
101
|
+
# configure_status_field <board_num> <owner>
|
|
102
|
+
# -----------------------------------------------------------------------------
|
|
103
|
+
configure_status_field() {
|
|
104
|
+
local BOARD_NUM="$1"
|
|
105
|
+
local OWNER="$2"
|
|
106
|
+
local STATUS_FIELD_ID
|
|
107
|
+
|
|
108
|
+
STATUS_FIELD_ID=$(gh project field-list "$BOARD_NUM" --owner "$OWNER" --format json \
|
|
109
|
+
--jq '.fields[] | select(.name=="Status") | .id')
|
|
110
|
+
|
|
111
|
+
if [ -z "$STATUS_FIELD_ID" ]; then
|
|
112
|
+
echo "ERROR: Status field not found on board $BOARD_NUM" >&2
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
gh api graphql -f query="
|
|
117
|
+
mutation {
|
|
118
|
+
updateProjectV2Field(input: {
|
|
119
|
+
fieldId: \"$STATUS_FIELD_ID\"
|
|
120
|
+
singleSelectOptions: $PARETO_STATUS_OPTIONS
|
|
121
|
+
}) {
|
|
122
|
+
projectV2Field { ... on ProjectV2SingleSelectField { id name } }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
" >/dev/null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# -----------------------------------------------------------------------------
|
|
129
|
+
# ensure_custom_fields <board_num> <owner>
|
|
130
|
+
# -----------------------------------------------------------------------------
|
|
131
|
+
ensure_custom_fields() {
|
|
132
|
+
local BOARD_NUM="$1"
|
|
133
|
+
local OWNER="$2"
|
|
134
|
+
|
|
135
|
+
# Priority (single-select)
|
|
136
|
+
gh project field-create "$BOARD_NUM" --owner "$OWNER" \
|
|
137
|
+
--name "Priority" --data-type "SINGLE_SELECT" \
|
|
138
|
+
--single-select-options "Must Have,Should Have,Could Have,Won't Have" \
|
|
139
|
+
2>/dev/null || true
|
|
140
|
+
|
|
141
|
+
# Sprint (iteration — created manually in UI if needed)
|
|
142
|
+
gh project field-create "$BOARD_NUM" --owner "$OWNER" \
|
|
143
|
+
--name "Sprint" --data-type "ITERATION" 2>/dev/null || true
|
|
144
|
+
|
|
145
|
+
# Estimate (number)
|
|
146
|
+
gh project field-create "$BOARD_NUM" --owner "$OWNER" \
|
|
147
|
+
--name "Estimate" --data-type "NUMBER" 2>/dev/null || true
|
|
148
|
+
|
|
149
|
+
# Phase (text — pp-core phase number reference, e.g. "3" or "3.1")
|
|
150
|
+
gh project field-create "$BOARD_NUM" --owner "$OWNER" \
|
|
151
|
+
--name "Phase" --data-type "TEXT" 2>/dev/null || true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# -----------------------------------------------------------------------------
|
|
155
|
+
# ensure_core_labels <owner/repo>
|
|
156
|
+
# -----------------------------------------------------------------------------
|
|
157
|
+
# Idempotent — uses --force so existing labels get updated.
|
|
158
|
+
ensure_core_labels() {
|
|
159
|
+
local REPO="$1"
|
|
160
|
+
|
|
161
|
+
gh label create "epic" -c "3E4B9E" -d "Epic — high-level feature group" --force -R "$REPO"
|
|
162
|
+
gh label create "user-story" -c "1D76DB" -d "User story" --force -R "$REPO"
|
|
163
|
+
gh label create "must-have" -c "B60205" -d "Must Have — MVP required" --force -R "$REPO"
|
|
164
|
+
gh label create "should-have" -c "FBCA04" -d "Should Have — important" --force -R "$REPO"
|
|
165
|
+
gh label create "could-have" -c "0E8A16" -d "Could Have — nice to have" --force -R "$REPO"
|
|
166
|
+
gh label create "in-progress" -c "FBCA04" -d "Phase branch actively executing" --force -R "$REPO"
|
|
167
|
+
gh label create "verification" -c "D93F0B" -d "In pp-core verification + PR review" --force -R "$REPO"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# -----------------------------------------------------------------------------
|
|
171
|
+
# create_epic_label <owner/repo> <slug> <epic_id> <epic_title> <index>
|
|
172
|
+
# -----------------------------------------------------------------------------
|
|
173
|
+
# Color cycles through PARETO_EPIC_COLORS by index.
|
|
174
|
+
create_epic_label() {
|
|
175
|
+
local REPO="$1"
|
|
176
|
+
local SLUG="$2"
|
|
177
|
+
local EPIC_ID="$3"
|
|
178
|
+
local TITLE="$4"
|
|
179
|
+
local IDX="$5"
|
|
180
|
+
local COLOR="${PARETO_EPIC_COLORS[$((IDX % ${#PARETO_EPIC_COLORS[@]}))]}"
|
|
181
|
+
|
|
182
|
+
gh label create "epic:${SLUG}" -c "$COLOR" -d "${EPIC_ID}: ${TITLE}" --force -R "$REPO"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# -----------------------------------------------------------------------------
|
|
186
|
+
# If sourced, functions above are exported to the caller. If executed directly,
|
|
187
|
+
# run create_project_board with positional args.
|
|
188
|
+
# -----------------------------------------------------------------------------
|
|
189
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
190
|
+
if [ $# -lt 3 ]; then
|
|
191
|
+
echo "Usage: $0 <owner> <title> <owner/repo>" >&2
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
create_project_board "$@"
|
|
195
|
+
fi
|