create-pmos 0.4.0 → 0.6.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.
Files changed (67) hide show
  1. package/README.md +149 -4
  2. package/bin/create-pmos.js +97 -8
  3. package/package.json +7 -3
  4. package/payload/AGENTS-HUMAN.md +6 -0
  5. package/payload/AGENTS.md +62 -3
  6. package/payload/KIT-VERSION +2 -2
  7. package/payload/README.md +16 -2
  8. package/payload/SECURITY.md +59 -0
  9. package/payload/okf/core/concepts/design-loop.md +6 -3
  10. package/payload/okf/core/concepts/frontmatter-format.md +75 -0
  11. package/payload/okf/core/concepts/index.md +3 -0
  12. package/payload/okf/core/concepts/log.md +13 -0
  13. package/payload/okf/core/templates/design-brief-template.md +1 -1
  14. package/payload/okf/core/templates/eval-rubric-template.md +12 -10
  15. package/payload/okf/core/templates/prd-template.md +6 -0
  16. package/payload/okf/core/templates/prototype-brief-template.md +45 -21
  17. package/payload/planning/evals/agent-run-eval-rubric.md +10 -7
  18. package/payload/scripts/frontier.sh +60 -0
  19. package/payload/scripts/metrics.py +163 -102
  20. package/payload/scripts/pmos-log.sh +16 -6
  21. package/payload/skills/SKILL-FORMAT.md +72 -9
  22. package/payload/skills/discovery.skill +4 -0
  23. package/payload/skills/eval-rubric.skill +1 -1
  24. package/payload/skills/evaluator.skill +65 -20
  25. package/payload/skills/explain-run.skill +9 -6
  26. package/payload/skills/explain-surface.skill +156 -0
  27. package/payload/skills/ingest-repo.skill +4 -3
  28. package/payload/skills/pmos-router.skill +25 -3
  29. package/payload/skills/prd.skill +8 -0
  30. package/payload/skills/prototype.skill +106 -41
  31. package/payload/skills-native/copywriting/SKILL.md +57 -0
  32. package/payload/skills-native/discovery/SKILL.md +75 -0
  33. package/payload/skills-native/eval-rubric/SKILL.md +159 -0
  34. package/payload/skills-native/evaluator/SKILL.md +182 -0
  35. package/payload/skills-native/explain-run/SKILL.md +121 -0
  36. package/payload/skills-native/explain-surface/SKILL.md +158 -0
  37. package/payload/skills-native/ingest-repo/SKILL.md +120 -0
  38. package/payload/skills-native/okr/SKILL.md +70 -0
  39. package/payload/skills-native/pmos-router/SKILL.md +81 -0
  40. package/payload/skills-native/prd/SKILL.md +117 -0
  41. package/payload/skills-native/prototype/SKILL.md +147 -0
  42. package/payload/state/tickets/.gitkeep +0 -0
  43. package/payload/templates/agent-run-eval-rubric.md +10 -7
  44. package/payload/templates/ticket-template.md +20 -0
  45. package/workspace/README.md +112 -0
  46. package/workspace/REMOTE.md +131 -0
  47. package/workspace/bin/pmos.js +206 -0
  48. package/workspace/doctor.js +99 -0
  49. package/workspace/lib/console-state.js +117 -0
  50. package/workspace/lib/file-mode-parse.js +58 -0
  51. package/workspace/lib/harness-parse.js +222 -0
  52. package/workspace/lib/preview-kind.js +98 -0
  53. package/workspace/lib/rate-limit.js +26 -0
  54. package/workspace/lib/remote-access.js +235 -0
  55. package/workspace/lib/review-gate.js +39 -0
  56. package/workspace/lib/schedule-logic.js +132 -0
  57. package/workspace/lib/skill-store.js +143 -0
  58. package/workspace/lib/surface-contract.js +79 -0
  59. package/workspace/lib/transcript-read.js +247 -0
  60. package/workspace/package-lock.json +94 -0
  61. package/workspace/package.json +23 -0
  62. package/workspace/public/app.js +1710 -0
  63. package/workspace/public/index.html +175 -0
  64. package/workspace/public/styles.css +766 -0
  65. package/workspace/server.js +1753 -0
  66. package/workspace/skills/backlog-triage.skill +13 -0
  67. package/workspace/skills/weekly-report.skill +19 -0
@@ -0,0 +1,75 @@
1
+ ---
2
+ type: Concept
3
+ title: Repo-wide frontmatter format
4
+ description: The PMOS house convention — every tracked .md carries OKF-format YAML frontmatter with a required type + title + timestamp, so agents and the harness classify and route a file by reading it, not by guessing from its path.
5
+ tags: [frontmatter, okf, format, harness, aci, convention]
6
+ timestamp: 2026-07-24
7
+ ---
8
+
9
+ # Repo-wide frontmatter format
10
+
11
+ **Situating context:** authored for the `repo-wide-md-frontmatter` initiative (proposed D63, amends
12
+ D03). The PM's framing — *"OKF is the format, not the folder"* — decouples the OKF **format** (which
13
+ this makes universal) from the `okf/` **bundle** (curated knowledge, unchanged) and from a file's
14
+ **location** (D04, unchanged). This concept is the reference for what the frontmatter lint enforces.
15
+
16
+ ## The rule
17
+
18
+ Every **tracked** markdown file MUST open with a YAML frontmatter block declaring three **required**
19
+ keys:
20
+
21
+ | key | meaning | rule |
22
+ |---|---|---|
23
+ | `type` | what the file *is* | one of the registry values (below); enforced |
24
+ | `title` | human-readable name | non-empty string |
25
+ | `timestamp` | last-modified | valid ISO 8601 date (`YYYY-MM-DD`) or datetime |
26
+
27
+ Recommended-but-optional (OKF): `description`, `tags`, `resource`. Any pre-existing producer keys
28
+ (`initiative_id`, `eval_type`, `parent_KR`, `pass_threshold`, …) are preserved as optional extension
29
+ keys — never stripped.
30
+
31
+ ```yaml
32
+ ---
33
+ type: Concept
34
+ title: Repo-wide frontmatter format
35
+ timestamp: 2026-07-24
36
+ ---
37
+ ```
38
+
39
+ ## Relationship to OKF and D03
40
+
41
+ OKF requires only `type`. PMOS layers `title` + `timestamp` as **required house keys**, applied
42
+ **repo-wide** (not just the `okf/` bundle). This is stricter than OKF's minimum but still
43
+ OKF-**conformant** — the spec is permissive about extra keys. It is the deliberate amendment D03
44
+ carries (type-only → richer set, repo-wide). The guardrail against required-key creep is this
45
+ registry: the enforced list of `type` values is closed and versioned.
46
+
47
+ ## Reserved files (exempt)
48
+
49
+ OKF's reserved filenames carry **no** frontmatter and are skipped by both the migration and the lint:
50
+
51
+ - `index.md` — a directory listing for progressive disclosure
52
+ - `log.md` — a chronological update history
53
+
54
+ ## The `type` registry
55
+
56
+ The enforced list lives in [`scripts/frontmatter_registry.txt`](/scripts/frontmatter_registry.txt) —
57
+ the single source `frontmatter_lint.py` reads. A `type:` value not in it fails CI (this catches typos).
58
+
59
+ - **OKF core:** `Concept`, `ADR`, `Template`, `Playbook`, `Reference`
60
+ - **Planning:** `PRD`, `Rubric`, `OKR`, `Radar`, `Checklist`, `Calibration`, `Research`, `Design`, `Prototype`
61
+ - **Root governance:** `Decision`, `Principle`, `Debt`, `Discovery`, `Guide`
62
+
63
+ ### Extending the vocabulary
64
+
65
+ Add **one line** to `scripts/frontmatter_registry.txt` and a matching row above. A new `type` is a
66
+ visible, reviewable diff — never an ad-hoc value invented in a single file.
67
+
68
+ ## Enforcement
69
+
70
+ - [`scripts/frontmatter_lint.py`](/scripts/frontmatter_lint.py) — deterministic, no secrets; asserts
71
+ the three required keys (and `type` ∈ registry) on every non-reserved tracked `.md`. Run in
72
+ `.github/workflows/frontmatter-ci.yml`.
73
+ - [`scripts/backfill_frontmatter.py`](/scripts/backfill_frontmatter.py) — the one-time migration:
74
+ prepend-only, idempotent; reuses a file's already-declared `type`/`date` when present, else infers
75
+ from path (`type`), the first H1 (`title`), and the last git-commit date (`timestamp`).
@@ -5,8 +5,10 @@ to pick concepts, then fetch the file(s) you need.
5
5
 
6
6
  - [agent-harness](agent-harness.md) — Everything wrapping the model that lets it finish work — and the Summary Gate, the input gate that compresses prior-session context into essential state.
7
7
  - [control-plane](control-plane.md) — What it means for PMOS to be a control plane — it orchestrates, specs, evaluates, and routes agent work rather than generating production code itself.
8
+ - [design-craft](design-craft.md) — The UI/UX practice a design agent applies: perception/cognition evidence, craft moves, accessibility floors, and recorded anti-patterns (D60).
8
9
  - [design-loop](design-loop.md) — How PMOS makes UI/UX design a first-class, repeatable sub-flow of the Build stage — brief → design → reconcile → build → design-sync — gated by the Review Gate, presentation-only, and never a data decision.
9
10
  - [eval-driven-pm](eval-driven-pm.md) — PMOS's core method — write the eval before the build. The rubric is the spec; the PM manages by defining what "good" means, not by inspecting work after the fact.
11
+ - [frontmatter-format](frontmatter-format.md) — The repo-wide house frontmatter (`type` + `title` + `timestamp` on every tracked `.md`) and the Frontmatter Gate + registry that enforce it (D63); machine-readable classification by reading, not path-guessing.
10
12
  - [health-anchors](health-anchors.md) — The second anchor class (D59): work anchors to a KR or a declared, capped, PM-reviewed health budget, so refactor/ops/security work anchors honestly.
11
13
  - [okf-governance](okf-governance.md) — How PMOS organizes its Open Knowledge Format bundle and the governance rules that keep it spec-conformant.
12
14
  - [okr-anti-optimism](okr-anti-optimism.md) — Why PMOS biases status reporting toward pessimism — surfacing the red underneath green metrics — to keep KR self-reporting honest.
@@ -15,4 +17,5 @@ to pick concepts, then fetch the file(s) you need.
15
17
  - [pm-identity](pm-identity.md) — How PMOS models a product manager — the pm_slug identifier, the PM's authority as the human in the loop, and PM-scoped namespaces.
16
18
  - [product-slug-convention](product-slug-convention.md) — The rules for product_slug — the stable lowercase identifier that scopes products across OKF namespaces, planning artifacts, and agent records.
17
19
  - [sdlc-loop](sdlc-loop.md) — The closed agent-driven software development lifecycle PMOS orchestrates — spec, contract, build, evaluate, accept, learn — and how it loops.
20
+ - [self-refining-harness](self-refining-harness.md) — The pattern where the harness improves from its own run evidence (evals, friction, calibration) via a detect → draft → PM-gate loop, bounded at the L3 ceiling — never L4 self-mutation.
18
21
  - [watermelon-flag](watermelon-flag.md) — A status that reads green on automated metrics but is red underneath on strategic alignment — and silent drift, the L4 failure mode it signals.
@@ -2,5 +2,18 @@
2
2
 
3
3
  OKF reserved file (no frontmatter). Change history, newest first, ISO 8601 dates.
4
4
 
5
+ ## 2026-07-24
6
+ - Added [frontmatter-format](frontmatter-format.md) — the repo-wide house frontmatter (`type` + `title` + `timestamp` on every tracked `.md`) + the Frontmatter Gate and registry (D63). Indexed.
7
+
8
+ ## 2026-07-21
9
+ - Added [design-craft](design-craft.md) — the UI/UX practice a design agent applies (perception/cognition evidence, craft moves, accessibility floors, recorded anti-patterns); promoted from `planning/design/attention-research.md` (D60). Indexed.
10
+
11
+ ## 2026-07-18
12
+ - Added [health-anchors](health-anchors.md) — the second anchor class: work anchors to a KR or a declared, capped, PM-reviewed health budget (D59). Indexed.
13
+
14
+ ## 2026-07-01
15
+ - Added [design-loop](design-loop.md) — UI/UX design as a Review-Gate-gated, presentation-only sub-flow of Build (brief → design → reconcile → build → design-sync); D48. Indexed.
16
+ - Added [self-refining-harness](self-refining-harness.md) — the harness improves from its own run evidence via detect → draft → PM-gate, bounded at L3 (D47). Indexed.
17
+
5
18
  ## 2026-06-29
6
19
  - Creation — per-folder index/log added (OKF recursive progressive disclosure; D29 reversal direction).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: Template
3
3
  title: Design Brief Template
4
- description: The scaffold for a design brief instance — the context handoff from Claude Code to claude.ai/design. Copy into /planning/design/ and fill; presentation-only.
4
+ description: The scaffold for a design brief instance — the context handoff from the interactive agent to an external design surface (e.g. claude.ai/design). Copy into /planning/design/ and fill; presentation-only.
5
5
  tags: [template, design, brief, planning]
6
6
  timestamp: 2026-07-01
7
7
  ---
@@ -20,17 +20,19 @@ OKF bundle.
20
20
  ---
21
21
 
22
22
  ```markdown
23
- # Eval Rubric: <initiative title>
23
+ ---
24
+ type: Rubric
25
+ title: <initiative title>
26
+ eval_type: capability | regression # capability = push frontier; regression = guard against backslide
27
+ initiative_id: <id>
28
+ product_slug: <product_slug>
29
+ pm_slug: <pm_slug>
30
+ parent_KR: <reference OR health:<id> (D59)>
31
+ pass_threshold: <0.00–1.00> # pre-committed, before output exists
32
+ timestamp: <YYYY-MM-DD>
33
+ ---
24
34
 
25
- ## Frontmatter
26
- - type: Rubric
27
- - eval_type: capability | regression <!-- capability = push frontier; regression = guard against backslide -->
28
- - initiative_id: <id>
29
- - product_slug: <product_slug>
30
- - pm_slug: <pm_slug>
31
- - parent_KR: <reference OR health:<id> (D59)>
32
- - pass_threshold: <0.00–1.00> <!-- pre-committed, before output exists -->
33
- - date: <YYYY-MM-DD>
35
+ # Eval Rubric: <initiative title>
34
36
 
35
37
  ## Gate assignment
36
38
  <!-- State which gate each criterion serves. Acceptance Gate is PM-only and NOT rubric-graded. -->
@@ -20,6 +20,12 @@ copy is an operational instance and does not live in the OKF bundle.
20
20
  ---
21
21
 
22
22
  ```markdown
23
+ ---
24
+ type: PRD
25
+ title: <initiative title>
26
+ timestamp: <YYYY-MM-DD>
27
+ ---
28
+
23
29
  # PRD: <initiative title>
24
30
 
25
31
  ## Identity
@@ -1,24 +1,28 @@
1
1
  ---
2
2
  type: Template
3
3
  title: Prototype Brief Template
4
- description: The scaffold for a QUICK leg-A prototype brief (D60) — the idea, the user, the moment, the feel, draft copy seeds. Deliberately short; if it needs PRD-level detail the flow has failed its purpose. Copy into prototype/<initiative-id>/brief.md and set it as the initiative's prd_path.
4
+ description: The scaffold for a QUICK leg-A prototype brief (D60) — the idea, the judgement it exercises, the user, the moment, the feel, draft copy seeds. Works for any medium, not just UI (2026-07-24 amendment). Deliberately short; if it needs PRD-level detail the flow has failed its purpose. Copy into prototype/<initiative-id>/brief.md and set it as the initiative's prd_path.
5
5
  tags: [template, prototype, design, lane]
6
- timestamp: 2026-07-21
6
+ timestamp: 2026-07-24
7
7
  ---
8
8
 
9
9
  # Prototype Brief Template
10
10
 
11
11
  **Situating context:** The [prototype lane](/okf/products/pmos/adr/d60-prototype-loop.md)'s contract
12
- artifact. The PM writes THIS — quick, sketchy, incomplete is fine — and the **design agent does the
13
- rest**: picks up the product's design system, applies the
14
- [design-craft layer](/okf/core/concepts/design-craft.md), drafts real copy, and returns divergent
15
- lookable options. The brief is deliberately NOT a PRD: it exists so the PM can see something *before*
16
- committing to a spec. If filling it in starts to feel like writing requirements, stop — that detail
17
- belongs in the PRD that follows a successful prototype, not here.
12
+ artifact. The PM writes THIS — quick, sketchy, incomplete is fine — and the **agent does the rest**:
13
+ picks up the artifact class's conventions (a design system for UI; SKILL-FORMAT, D19 ACI,
14
+ migration/RLS conventions elsewhere), applies that class's craft, drafts real content, and returns
15
+ divergent specimens. The brief is deliberately NOT a PRD: it exists so the PM can examine something
16
+ *before* committing to a spec. If filling it in starts to feel like writing requirements, stop — that
17
+ detail belongs in the PRD that follows a successful prototype, not here.
18
+
19
+ **Any medium** (2026-07-24 amendment to D60): the lane is not UI-gated. A specimen may be a lookable
20
+ screen, a worked sample output, call/response transcripts, or a schema sketch — whichever is the
21
+ cheapest thing that lets the PM exercise the judgement below.
18
22
 
19
23
  > Usage: copy into `prototype/<initiative-id>/brief.md`; set it as the initiative's `prd_path`
20
24
  > (`create_initiative`, `type: 'prototype'`). Every section may be a single sentence. Only the first
21
- > two are required.
25
+ > three are required.
22
26
 
23
27
  ---
24
28
 
@@ -35,30 +39,50 @@ belongs in the PRD that follows a successful prototype, not here.
35
39
  ## Who it's for, and the moment they'd use it (required)
36
40
  <!-- The user and the situation — "a PM opening the board on Monday morning", not a persona doc. -->
37
41
 
42
+ ## The judgement you need to make (required)
43
+ <!-- One line: what you cannot decide from a description alone, and so need to SEE an instance of.
44
+ "Whether this actually reads as calm rather than sparse." "Whether this explainer actually
45
+ teaches someone who's never seen the region." "Whether this API feels right to call."
46
+ This is the specimen test written down — it steers the format, the grilling, and the
47
+ disposition. If you can't name such a judgement, this may not be a prototype (see the
48
+ discovery lane, whose deliverable is a learning, not a validated direction). -->
49
+
50
+ ## Specimen format
51
+ <!-- Leave blank to let the agent propose it and say why. The principle is the CHEAPEST thing that
52
+ lets you exercise the judgement above: lookable HTML, a worked sample output on real input,
53
+ call/response transcripts, a schema sketch with example rows, the actual strings. -->
54
+
38
55
  ## How it should feel
39
56
  <!-- Adjectives and references beat specification: "calm, dense, fast to scan", "more like a
40
- cockpit than a report". Leave blank to let the agent propose. -->
57
+ cockpit than a report". Applies beyond visuals — an API can feel obvious or fiddly, a
58
+ sample output can feel patronising or brisk. Leave blank to let the agent propose. -->
41
59
 
42
60
  ## Copy seeds
43
- <!-- Any words you already hear: a headline, a button label, the tone. The agent drafts REAL copy
44
- (D60 — copy rides the prototype, no lorem); seeds steer it. Blank is fine. -->
61
+ <!-- Any words you already hear: a headline, a button label, an error string, the tone. The agent
62
+ drafts REAL content (D60 — copy rides the prototype, no lorem, and no fake inputs/outputs for
63
+ non-visual specimens: placeholders hide the friction the specimen exists to surface). Blank is
64
+ fine. -->
45
65
 
46
66
  ## What this is NOT
47
67
  <!-- One line if you already know a direction to avoid. -->
48
68
 
49
- ## Existing surface?
50
- <!-- If this improves an existing screen/flow, name it. New idea → leave blank. -->
69
+ ## Existing thing?
70
+ <!-- If this improves something that already exists — a screen, a skill, an endpoint — name it.
71
+ Brand-new idea → leave blank. -->
51
72
  ```
52
73
 
53
74
  ---
54
75
 
55
76
  ## What the agent does with this (not the PM's job)
56
77
 
57
- 1. **Design-system check first (blocking, D60):** no design system on this product → the agent
58
- authors one from this same brief and the PM approves it *before* any prototype.
59
- 2. Produces **divergent low-fidelity options** — one safe direction is a weaker outcome than three
60
- arguable ones — with **real draft copy**, into `prototype/<initiative-id>/`.
61
- 3. Submits to **designer grilling** (dialogue, never a score — D60): one question at a time, a
62
- recommended answer with each.
78
+ 1. **Conventions check first (blocking, D60(c) as generalised):** the artifact class has no
79
+ conventions and no craft to reason from → the agent proposes a minimal set from this same brief
80
+ and the PM approves it *before* any prototype. For UI that is the design system; for a skill,
81
+ SKILL-FORMAT; for a tool or API, D19's ACI principle; for a schema, the migration/RLS conventions.
82
+ 2. Produces **2–3 divergent low-fidelity specimens** — one safe direction is a weaker outcome than
83
+ three arguable ones — with **real content**, into `prototype/<initiative-id>/`.
84
+ 3. Submits to a **craft-grade grilling** (dialogue, never a score — D60(d)): one question at a time,
85
+ a recommended answer with each. This holds for non-visual specimens too, where the temptation to
86
+ score is sharper.
63
87
  4. The PM **disposes**: promote (→ a new feature-lane initiative; PRD + rubric derived from the
64
- validated option) / iterate / kill.
88
+ validated specimen) / iterate / kill.
@@ -1,3 +1,13 @@
1
+ ---
2
+ type: Rubric
3
+ title: "Eval Rubric: Agent-run capability baseline (kit seed — copy per initiative)"
4
+ timestamp: 2026-07-19
5
+ eval_type: capability
6
+ initiative_id: (set per initiative)
7
+ pass_threshold: 0.80
8
+ canonical_path: pmos/planning/evals/agent-run-eval-rubric.md
9
+ ---
10
+
1
11
  # Eval Rubric: Agent-run capability baseline (kit seed — copy per initiative)
2
12
 
3
13
  > Kit-seeded generic rubric (D54). Two purposes: (1) it is the TEMPLATE to copy to
@@ -6,13 +16,6 @@
6
16
  > meaningful from day one. It scores a generic agent run outcome-only (D14) with weighted partial
7
17
  > credit (D15).
8
18
 
9
- ## Frontmatter
10
- - type: Rubric
11
- - eval_type: capability
12
- - initiative_id: (set per initiative)
13
- - pass_threshold: 0.80
14
- - canonical_path: pmos/planning/evals/agent-run-eval-rubric.md
15
-
16
19
  ## Gate assignment
17
20
  - **Quality Gate (deterministic):** repo CI green; `pmos-gate` (anchored + rubrics) green.
18
21
  - **Review Gate (adversarial):** the weighted dimensions below — evaluator skill, default-to-refute,
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ # frontier.sh — list the workable tickets in a file-mode PMOS kit (E9).
3
+ #
4
+ # "Work the frontier": a ticket is workable when it is NOT done and EVERY ticket it is blocked_by is
5
+ # done. This is the plain-file equivalent of a board's dependency structure — one file per ticket in
6
+ # state/tickets/NNN-slug.md, numbered in dependency order, blocking edges as text.
7
+ #
8
+ # scripts/frontier.sh # from the kit root; lists workable tickets
9
+ # scripts/frontier.sh state/tickets # explicit tickets dir
10
+ #
11
+ # Dependency-free and portable: POSIX-ish bash, no associative arrays (runs on bash 3.2, e.g. stock
12
+ # macOS). No network, no board. Reads only the YAML frontmatter fields id / status / blocked_by.
13
+ set -euo pipefail
14
+
15
+ DIR="${1:-state/tickets}"
16
+ [ -d "$DIR" ] || { echo "frontier: no tickets dir at '$DIR' (nothing to do)"; exit 0; }
17
+
18
+ # --- field readers (frontmatter only: stop at the closing --- fence) ----------------------------
19
+ fm() { awk 'NR>1 && /^---[[:space:]]*$/{exit} {print}' "$1"; } # lines between the opening and closing ---
20
+ field() { printf '%s\n' "$1" | sed -n "s/^$2:[[:space:]]*//p" | head -1 | sed 's/^["'\'']//;s/["'\'']$//;s/[[:space:]]*$//'; }
21
+ # blocked_by is an inline list "[\"002\", \"005\"]"; extract the bare ids (digits) in order.
22
+ blockers() { printf '%s\n' "$1" | sed -n 's/^blocked_by:[[:space:]]*//p' | head -1 | grep -oE '[0-9]+' || true; }
23
+ # derive an id: explicit `id:` field, else the leading NNN of the filename.
24
+ idof() { local i; i="$(field "$1" id)"; [ -n "$i" ] && { printf '%s' "$i"; return; }; basename "$2" | grep -oE '^[0-9]+' || basename "$2" .md; }
25
+
26
+ shopt -s nullglob
27
+ files=("$DIR"/*.md)
28
+ [ ${#files[@]} -eq 0 ] && { echo "frontier: no tickets in '$DIR' (nothing to do)"; exit 0; }
29
+
30
+ # --- pass 1: collect the done ids (newline-delimited; no associative arrays for bash-3.2 portability)
31
+ DONE_IDS=""
32
+ OPEN=0
33
+ for f in "${files[@]}"; do
34
+ meta="$(fm "$f")"; id="$(idof "$meta" "$f")"; st="$(field "$meta" status)"
35
+ if [ "$st" = "done" ]; then DONE_IDS="$DONE_IDS$id
36
+ "; else OPEN=$((OPEN+1)); fi
37
+ done
38
+ is_done() { printf '%s' "$DONE_IDS" | grep -qx "$1"; }
39
+
40
+ # --- pass 2: a ticket is on the frontier when not done and every blocker is done -----------------
41
+ found=0
42
+ for f in "${files[@]}"; do
43
+ meta="$(fm "$f")"; id="$(idof "$meta" "$f")"; st="$(field "$meta" status)"; st="${st:-todo}"
44
+ [ "$st" = "done" ] && continue
45
+ workable=1
46
+ while IFS= read -r b; do
47
+ [ -z "$b" ] && continue
48
+ is_done "$b" || workable=0
49
+ done < <(blockers "$meta")
50
+ if [ "$workable" -eq 1 ]; then
51
+ printf ' ▸ %-5s %-8s %s\n' "$id" "[$st]" "$(field "$meta" title)"
52
+ found=$((found+1))
53
+ fi
54
+ done
55
+
56
+ if [ "$found" -eq 0 ]; then
57
+ # distinguish "all done" from "everything is blocked" — a stuck frontier is a real signal.
58
+ if [ "$OPEN" -eq 0 ]; then echo "frontier: all tickets done ✓"
59
+ else echo "frontier: $OPEN open ticket(s), NONE workable — the frontier is blocked (check for a cycle or an undone blocker)."; fi
60
+ fi