create-pmos 0.57.0 → 0.58.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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/first-paint-audit.js +5 -2
- package/workspace/guide/docs.md +1 -1
- package/workspace/guide/index.md +2 -1
- package/workspace/guide/jira.md +141 -0
- package/workspace/guide/layout.md +2 -2
- package/workspace/lib/jira-cli.js +194 -0
- package/workspace/lib/jira-status.js +239 -0
- package/workspace/lib/jira-tickets.js +668 -0
- package/workspace/lib/pm-guard.js +2 -0
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/report-draft.js +19 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +206 -0
- package/workspace/public/pm/dest-docs.js +1 -1
- package/workspace/public/pm/frame-copy.js +1 -0
- package/workspace/public/pm/initiative-sheet.js +7 -3
- package/workspace/public/pm/initiatives-copy.js +1 -0
- package/workspace/public/pm/sheet-jira.js +353 -0
- package/workspace/public/pm/topbar.js +5 -4
- package/workspace/server.js +69 -0
- package/workspace/skills/day-check.skill +1 -1
- package/workspace/skills/gather-requirements.skill +1 -1
- package/workspace/skills/jira-push.skill +38 -0
- package/workspace/skills/jira-sync.skill +42 -0
- package/workspace/skills/morning-brief.skill +1 -1
- package/workspace/skills/report-biweekly.skill +1 -1
- package/workspace/skills/tracker-refresh.skill +1 -0
- package/workspace/test-lists/jira-wbs.json +6 -0
|
@@ -54,6 +54,7 @@ const PM_ONLY_ACTS = Object.freeze([
|
|
|
54
54
|
'bind', // bind a session to an initiative (/api/session/initiative)
|
|
55
55
|
'issue', // Issue a report (issues.jsonl)
|
|
56
56
|
'release', // the D65 Release of a factory batch (one grant per batch)
|
|
57
|
+
'push', // select Jira tickets for a push (D88: /api/jira/push, pmos jira approve)
|
|
57
58
|
]);
|
|
58
59
|
/** What each act is, as the refusal says it: "only the PM can <phrase>". */
|
|
59
60
|
const ACT_PHRASE = Object.freeze({
|
|
@@ -66,6 +67,7 @@ const ACT_PHRASE = Object.freeze({
|
|
|
66
67
|
bind: 'bind a session to an initiative',
|
|
67
68
|
issue: 'issue a report',
|
|
68
69
|
release: 'release a batch',
|
|
70
|
+
push: 'select tickets to push to Jira',
|
|
69
71
|
});
|
|
70
72
|
/** Fields only a writer sets. A body that carries one is refused, even with the value the writer would choose. */
|
|
71
73
|
const WRITER_FIELDS = Object.freeze(['v', 'eid', 'by', 'via', 'at', 'override', 'pm', 'actor', 'nonce', 'grant']);
|
|
@@ -34,6 +34,9 @@ const VERBS = Object.freeze({
|
|
|
34
34
|
help: 'pmos verify-work — mark work items integrated from the default branch (local refs only)' }),
|
|
35
35
|
report: Object.freeze({ module: 'report-cli.js', run: 5, guide: 'reports.md',
|
|
36
36
|
help: 'pmos report draft|issue|list … — draft and issue reports (pmos report help)' }),
|
|
37
|
+
// Not a pm-os-absorb verb (run: null): initiative jira-wbs added it through the same table.
|
|
38
|
+
jira: Object.freeze({ module: 'jira-cli.js', run: null, guide: 'jira.md',
|
|
39
|
+
help: 'pmos jira draft|show|epic|approve|pending|record|query|sync|which … — Jira tickets drafted from the PRD, and their status (pmos jira help)' }),
|
|
37
40
|
// Not a pm-os-absorb verb (run: null): the workspace-launcher initiative added it through the same table.
|
|
38
41
|
// Not a pm-os-absorb verb (run: null): docs-three-way-sync added it through the same table.
|
|
39
42
|
docs: Object.freeze({ module: 'docs-cli.js', run: null, guide: 'docs.md',
|
|
@@ -431,12 +431,31 @@ function draftOf(root, { cadence, slot, now, health } = {}) {
|
|
|
431
431
|
const pmosWritten = (ref) => /\.jsonl$/.test(ref) || /(^|\/)(runs|reports)\//.test(ref);
|
|
432
432
|
const isEvidence = (h) => h.ev === 'moved' && h.evidence && h.evidence.kind === 'file' && isStr(h.evidence.ref) && !pmosWritten(h.evidence.ref);
|
|
433
433
|
const trackerItems = fold.order.map((x) => fold.items[x]).filter((it) => it && work.has(it.initiative) && isStr(it.owner) && it.owner.startsWith('tracker:'));
|
|
434
|
+
/* The Jira status check (initiative jira-wbs, J14) writes a snapshot per initiative each time an agent read Jira. Its
|
|
435
|
+
* timestamp is a read of the tracker on its own, with no plan item: the check writes none (D88). */
|
|
436
|
+
const jiraReads = (() => {
|
|
437
|
+
const dir = path.join(path.dirname(L.initiatives), 'tracker', 'jira');
|
|
438
|
+
let names = [];
|
|
439
|
+
try { names = fs.readdirSync(dir).filter((n) => n.endsWith('.md') && !n.endsWith('-tickets.md') && work.has(n.slice(0, -3))); } catch { return []; }
|
|
440
|
+
const out = [];
|
|
441
|
+
for (const n of names) {
|
|
442
|
+
let fm = null;
|
|
443
|
+
try { fm = E.parseLeadingFrontmatter(fs.readFileSync(path.join(dir, n), 'utf8')); } catch { fm = null; }
|
|
444
|
+
if (fm && isStr(fm.timestamp)) out.push(fm.timestamp);
|
|
445
|
+
}
|
|
446
|
+
return out.sort();
|
|
447
|
+
})();
|
|
434
448
|
if (trackerItems.length) {
|
|
435
449
|
const seen = trackerItems.flatMap((it) => it.history.filter(isEvidence).map((h) => String(h.at))).sort();
|
|
436
450
|
const lastSeen = seen.length ? seen.pop() : null;
|
|
437
451
|
if (lastSeen && inWin(lastSeen)) reached.push('tracker (file evidence on tracker-owned moves in plan-items)');
|
|
438
452
|
else notReached.push({ source: 'tracker', why: lastSeen ? `not refreshed since ${short(lastSeen)}` : 'no refresh with file evidence on record' });
|
|
439
453
|
}
|
|
454
|
+
if (jiraReads.length) {
|
|
455
|
+
const lastRead = jiraReads[jiraReads.length - 1];
|
|
456
|
+
if (inWin(lastRead)) reached.push(`Jira (status check ${short(lastRead)})`);
|
|
457
|
+
else notReached.push({ source: 'Jira', why: `not checked since ${short(lastRead)}` });
|
|
458
|
+
}
|
|
440
459
|
const fetched = lastFetch(abs);
|
|
441
460
|
if (fetched) reached.push(`remote refs (last fetch ${short(fetched)}; not fetched for this draft)`);
|
|
442
461
|
else notReached.push({ source: 'remote refs', why: 'never fetched; this draft reads local refs only' });
|
package/workspace/package.json
CHANGED
|
@@ -1853,6 +1853,212 @@
|
|
|
1853
1853
|
}
|
|
1854
1854
|
|
|
1855
1855
|
|
|
1856
|
+
/* the initiative sheet's Jira tickets tab (initiative jira-wbs, sheet-jira.js) */
|
|
1857
|
+
.in-jira {
|
|
1858
|
+
display: flex;
|
|
1859
|
+
flex-direction: column;
|
|
1860
|
+
gap: 12px;
|
|
1861
|
+
min-width: 0;
|
|
1862
|
+
}
|
|
1863
|
+
.in-jira-head {
|
|
1864
|
+
display: flex;
|
|
1865
|
+
flex-wrap: wrap;
|
|
1866
|
+
align-items: flex-end;
|
|
1867
|
+
justify-content: space-between;
|
|
1868
|
+
gap: 8px 16px;
|
|
1869
|
+
}
|
|
1870
|
+
.in-jira-epic {
|
|
1871
|
+
display: flex;
|
|
1872
|
+
flex-wrap: wrap;
|
|
1873
|
+
align-items: flex-end;
|
|
1874
|
+
gap: 8px;
|
|
1875
|
+
min-width: 0;
|
|
1876
|
+
}
|
|
1877
|
+
.in-jira-epic__text,
|
|
1878
|
+
.in-jira-count {
|
|
1879
|
+
font-size: 14px;
|
|
1880
|
+
font-weight: 600;
|
|
1881
|
+
}
|
|
1882
|
+
.in-jira-status-line {
|
|
1883
|
+
display: flex;
|
|
1884
|
+
flex-wrap: wrap;
|
|
1885
|
+
align-items: center;
|
|
1886
|
+
gap: 4px 12px;
|
|
1887
|
+
}
|
|
1888
|
+
.in-jira-when {
|
|
1889
|
+
font-size: 12px;
|
|
1890
|
+
color: var(--muted-foreground);
|
|
1891
|
+
}
|
|
1892
|
+
.in-jira-note {
|
|
1893
|
+
display: flex;
|
|
1894
|
+
flex-wrap: wrap;
|
|
1895
|
+
align-items: center;
|
|
1896
|
+
gap: 8px 12px;
|
|
1897
|
+
padding: 8px 12px;
|
|
1898
|
+
border: 1px solid var(--border);
|
|
1899
|
+
}
|
|
1900
|
+
.in-jira-note__text {
|
|
1901
|
+
flex: 1 1 200px;
|
|
1902
|
+
font-size: 14px;
|
|
1903
|
+
}
|
|
1904
|
+
.in-jira-list {
|
|
1905
|
+
display: flex;
|
|
1906
|
+
flex-direction: column;
|
|
1907
|
+
border-top: 1px solid var(--border);
|
|
1908
|
+
}
|
|
1909
|
+
.in-jira-pick-gap {
|
|
1910
|
+
display: inline-block;
|
|
1911
|
+
width: 16px;
|
|
1912
|
+
}
|
|
1913
|
+
.in-jira-where,
|
|
1914
|
+
.in-jira-sub {
|
|
1915
|
+
font-size: 12px;
|
|
1916
|
+
color: var(--muted-foreground);
|
|
1917
|
+
white-space: nowrap;
|
|
1918
|
+
}
|
|
1919
|
+
.in-jira-actions {
|
|
1920
|
+
display: flex;
|
|
1921
|
+
flex-wrap: wrap;
|
|
1922
|
+
align-items: center;
|
|
1923
|
+
gap: 8px;
|
|
1924
|
+
}
|
|
1925
|
+
.in-jira-detail-top {
|
|
1926
|
+
display: flex;
|
|
1927
|
+
flex-wrap: wrap;
|
|
1928
|
+
align-items: center;
|
|
1929
|
+
gap: 8px;
|
|
1930
|
+
}
|
|
1931
|
+
.in-jira-detail-top__gap {
|
|
1932
|
+
flex: 1 1 auto;
|
|
1933
|
+
}
|
|
1934
|
+
.in-jira-detail {
|
|
1935
|
+
display: flex;
|
|
1936
|
+
flex-direction: column;
|
|
1937
|
+
gap: 12px;
|
|
1938
|
+
min-width: 0;
|
|
1939
|
+
}
|
|
1940
|
+
.in-jira-detail__title {
|
|
1941
|
+
margin: 0;
|
|
1942
|
+
font-size: 20px;
|
|
1943
|
+
line-height: 28px;
|
|
1944
|
+
font-weight: 600;
|
|
1945
|
+
overflow-wrap: anywhere;
|
|
1946
|
+
}
|
|
1947
|
+
.in-jira-detail__meta {
|
|
1948
|
+
font-size: 12px;
|
|
1949
|
+
color: var(--muted-foreground);
|
|
1950
|
+
}
|
|
1951
|
+
.in-jira-warn {
|
|
1952
|
+
margin: 0;
|
|
1953
|
+
padding: 8px 12px;
|
|
1954
|
+
border: 1px solid var(--warning);
|
|
1955
|
+
font-size: 14px;
|
|
1956
|
+
}
|
|
1957
|
+
.in-jira-fields {
|
|
1958
|
+
display: grid;
|
|
1959
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1960
|
+
gap: 8px 12px;
|
|
1961
|
+
}
|
|
1962
|
+
.in-jira-box {
|
|
1963
|
+
display: flex;
|
|
1964
|
+
flex-wrap: wrap;
|
|
1965
|
+
align-items: center;
|
|
1966
|
+
gap: 4px 16px;
|
|
1967
|
+
padding: 10px 12px;
|
|
1968
|
+
border: 1px solid var(--border);
|
|
1969
|
+
}
|
|
1970
|
+
.in-jira-box__key {
|
|
1971
|
+
font-family: var(--mono);
|
|
1972
|
+
font-size: 14px;
|
|
1973
|
+
font-weight: 600;
|
|
1974
|
+
}
|
|
1975
|
+
.in-jira-box__where {
|
|
1976
|
+
font-size: 14px;
|
|
1977
|
+
}
|
|
1978
|
+
.in-jira-box__sub {
|
|
1979
|
+
font-size: 12px;
|
|
1980
|
+
color: var(--muted-foreground);
|
|
1981
|
+
}
|
|
1982
|
+
.in-jira-box__link {
|
|
1983
|
+
margin-left: auto;
|
|
1984
|
+
font-size: 12px;
|
|
1985
|
+
color: var(--foreground);
|
|
1986
|
+
text-underline-offset: 2px;
|
|
1987
|
+
}
|
|
1988
|
+
.in-jira-diff {
|
|
1989
|
+
display: flex;
|
|
1990
|
+
flex-direction: column;
|
|
1991
|
+
gap: 8px;
|
|
1992
|
+
padding: 10px 12px;
|
|
1993
|
+
border: 1px solid var(--destructive);
|
|
1994
|
+
}
|
|
1995
|
+
.in-jira-diff__title {
|
|
1996
|
+
margin: 0;
|
|
1997
|
+
font-size: 14px;
|
|
1998
|
+
font-weight: 600;
|
|
1999
|
+
}
|
|
2000
|
+
.in-jira-diff__sides {
|
|
2001
|
+
display: grid;
|
|
2002
|
+
grid-template-columns: 1fr 1fr;
|
|
2003
|
+
gap: 12px;
|
|
2004
|
+
}
|
|
2005
|
+
.in-jira-diff__side {
|
|
2006
|
+
display: flex;
|
|
2007
|
+
flex-direction: column;
|
|
2008
|
+
gap: 8px;
|
|
2009
|
+
min-width: 0;
|
|
2010
|
+
font-size: 14px;
|
|
2011
|
+
}
|
|
2012
|
+
.in-jira-diff__label,
|
|
2013
|
+
.in-jira-diff__name,
|
|
2014
|
+
.in-jira-body__name {
|
|
2015
|
+
margin: 0;
|
|
2016
|
+
font-size: 12px;
|
|
2017
|
+
font-weight: 600;
|
|
2018
|
+
letter-spacing: 0.1em;
|
|
2019
|
+
text-transform: uppercase;
|
|
2020
|
+
color: var(--muted-foreground);
|
|
2021
|
+
}
|
|
2022
|
+
.in-jira-diff__part,
|
|
2023
|
+
.in-jira-body__part {
|
|
2024
|
+
display: flex;
|
|
2025
|
+
flex-direction: column;
|
|
2026
|
+
gap: 4px;
|
|
2027
|
+
min-width: 0;
|
|
2028
|
+
overflow-wrap: anywhere;
|
|
2029
|
+
}
|
|
2030
|
+
.in-jira-body,
|
|
2031
|
+
.in-jira-edit {
|
|
2032
|
+
display: flex;
|
|
2033
|
+
flex-direction: column;
|
|
2034
|
+
gap: 16px;
|
|
2035
|
+
font-size: 14px;
|
|
2036
|
+
line-height: 20px;
|
|
2037
|
+
}
|
|
2038
|
+
.in-jira-body__empty {
|
|
2039
|
+
margin: 0;
|
|
2040
|
+
color: var(--muted-foreground);
|
|
2041
|
+
}
|
|
2042
|
+
.in-jira-area {
|
|
2043
|
+
min-height: 96px;
|
|
2044
|
+
}
|
|
2045
|
+
@media (max-width: 640px) {
|
|
2046
|
+
.in-jira-fields {
|
|
2047
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2048
|
+
}
|
|
2049
|
+
.in-jira-diff__sides {
|
|
2050
|
+
grid-template-columns: 1fr;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
@media (pointer: coarse) {
|
|
2054
|
+
.in-jira-box__link {
|
|
2055
|
+
display: inline-flex;
|
|
2056
|
+
align-items: center;
|
|
2057
|
+
min-height: 44px;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
|
|
1856
2062
|
/* -- layouts · .fr- the frame: top bar, dock, Settings (Run 3, slice 3A) -- */
|
|
1857
2063
|
/* The frame's root: the top bar over the body. app.js drops the cockpit's inline geometry when the frame is on. */
|
|
1858
2064
|
.fr-root {
|
|
@@ -66,7 +66,7 @@ export const COPY = Object.freeze({
|
|
|
66
66
|
started: 'Compare started. It reads every copy and changes nothing.',
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
// Docs is
|
|
69
|
+
// Docs is in the top bar (the PM, 2026-10-06); ⌘K and `g d` open it too.
|
|
70
70
|
registerCommand({ id: 'docs-open', label: 'Docs', hint: 'Is each document the same as its Google Docs and Confluence copy', cli: 'pmos docs list', run: () => openDestination('docs') });
|
|
71
71
|
|
|
72
72
|
/* ── words ───────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* About 70% of the width on a desktop and the whole screen on a phone. Top to bottom:
|
|
4
4
|
* line 1 the title, the horizon, the health reason (only when not on track), and a meta line: lane · anchor · id
|
|
5
5
|
* line 2 the stage stepper, then "Waiting on you: approval [Open gate ▸]" or "Waiting on agent: build [Hand to agent ▸]"
|
|
6
|
-
* tabs Breakdown (the Run 1 pane, embedded: Tree · Board, and Timeline once Run 4 registers it), Files, History
|
|
6
|
+
* tabs Breakdown (the Run 1 pane, embedded: Tree · Board, and Timeline once Run 4 registers it), Files, History,
|
|
7
|
+
* Jira tickets (sheet-jira.js, initiative jira-wbs: the tickets drafted from the PRD and their status in Jira)
|
|
7
8
|
* ⋯ horizon, check-by, lifecycle and the anchor (both PM-only: the server's guard answers), Copy CLI, Export breakdown
|
|
8
9
|
* Nothing on this sheet says "plan". Everything it shows is read from GET /api/initiatives/<id>; every write goes through
|
|
9
10
|
* initiatives-api.js. The Breakdown tab mounts with a tab id of the form `initiative:<slug>`, so it never meets a panel-4
|
|
@@ -17,6 +18,7 @@ import { el } from './dom.js';
|
|
|
17
18
|
import { sheet, button, iconButton, tabs, row, badge, menu, toast, empty, field, ensureComponentsCss } from './primitives.js';
|
|
18
19
|
import { icon, ensureSprite } from './icons.js';
|
|
19
20
|
import { mountBreakdown, unmountBreakdown } from './panel-breakdown.js';
|
|
21
|
+
import { mountJira, unmountJira } from './sheet-jira.js';
|
|
20
22
|
import { openDoc } from './panel-initiatives.js';
|
|
21
23
|
import { openNewSession } from './panel-newsess.js';
|
|
22
24
|
import { wsOf, readDoc, saveDoc } from './data.js';
|
|
@@ -73,7 +75,7 @@ export function openSheet(repo, slug) {
|
|
|
73
75
|
const s = sheet({
|
|
74
76
|
mode: 'auto', title: slug, sub: '', body: [crumb, stepperHost, waitingHost, tabsHost, panel],
|
|
75
77
|
attrs: { class: 'in-sheet', 'data-in-sheet': 'initiative', 'data-in-id': slug },
|
|
76
|
-
onClose: () => { closed = true; unmountBreakdown(panel); if (current === s) current = null; },
|
|
78
|
+
onClose: () => { closed = true; unmountBreakdown(panel); unmountJira(panel); if (current === s) current = null; },
|
|
77
79
|
});
|
|
78
80
|
current = s;
|
|
79
81
|
|
|
@@ -219,10 +221,11 @@ export function openSheet(repo, slug) {
|
|
|
219
221
|
function drawTabs() {
|
|
220
222
|
tabsHost.replaceChildren(tabs({
|
|
221
223
|
variant: 'segmented', full: true, ariaLabel: COPY.sheet.tabs, value: tab,
|
|
222
|
-
items: [{ id: 'breakdown', label: COPY.sheet.breakdown }, { id: 'files', label: COPY.sheet.files }, { id: 'history', label: COPY.sheet.history }],
|
|
224
|
+
items: [{ id: 'breakdown', label: COPY.sheet.breakdown }, { id: 'files', label: COPY.sheet.files }, { id: 'history', label: COPY.sheet.history }, { id: 'jira', label: COPY.sheet.jira }],
|
|
223
225
|
onChange: (id) => {
|
|
224
226
|
if (id === tab) return;
|
|
225
227
|
if (tab === 'breakdown') unmountBreakdown(panel);
|
|
228
|
+
if (tab === 'jira') unmountJira(panel);
|
|
226
229
|
tab = id;
|
|
227
230
|
drawTabs(); drawPanel();
|
|
228
231
|
const t = tabsHost.querySelector('[data-tab="' + id + '"]');
|
|
@@ -234,6 +237,7 @@ export function openSheet(repo, slug) {
|
|
|
234
237
|
function drawPanel() {
|
|
235
238
|
if (!data) return;
|
|
236
239
|
if (tab === 'breakdown') { mountBreakdown(panel, { id: tabId, ws, slug }, { embedded: true }); return; }
|
|
240
|
+
if (tab === 'jira') { mountJira(panel, { ws, slug }); return; }
|
|
237
241
|
if (tab === 'files') {
|
|
238
242
|
const files = data.files || [];
|
|
239
243
|
panel.replaceChildren(files.length ? el('div', { class: 'in-sheet__rows', role: 'list' }, files.map((f) => row({
|
|
@@ -120,6 +120,7 @@ export const COPY = {
|
|
|
120
120
|
breakdown: 'Breakdown',
|
|
121
121
|
files: 'Files',
|
|
122
122
|
history: 'History',
|
|
123
|
+
jira: 'Jira tickets',
|
|
123
124
|
waitingYou: (act) => `Waiting on you: ${act}`,
|
|
124
125
|
waitingAgent: (act) => `Waiting on agent: ${act}`,
|
|
125
126
|
nothingOwed: 'Nothing is owed.',
|