@davidorex/pi-workflows 0.14.4 → 0.26.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/README.md +12 -9
- package/agents/handoff-writer.agent.yaml +1 -1
- package/dist/agent-spec.d.ts.map +1 -1
- package/dist/agent-spec.js +4 -3
- package/dist/agent-spec.js.map +1 -1
- package/dist/bundled-dirs.d.ts +2 -0
- package/dist/bundled-dirs.d.ts.map +1 -0
- package/dist/bundled-dirs.js +21 -0
- package/dist/bundled-dirs.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +118 -8
- package/dist/index.js.map +1 -1
- package/dist/render-by-id.d.ts +62 -0
- package/dist/render-by-id.d.ts.map +1 -0
- package/dist/render-by-id.js +102 -0
- package/dist/render-by-id.js.map +1 -0
- package/dist/step-agent.js +1 -1
- package/dist/step-block.d.ts +5 -1
- package/dist/step-block.d.ts.map +1 -1
- package/dist/step-block.js +88 -40
- package/dist/step-block.js.map +1 -1
- package/dist/step-monitor.d.ts +1 -1
- package/dist/step-monitor.d.ts.map +1 -1
- package/dist/step-monitor.js +1 -1
- package/dist/step-monitor.js.map +1 -1
- package/dist/step-shared.d.ts +1 -1
- package/dist/step-shared.d.ts.map +1 -1
- package/dist/step-shared.js +16 -10
- package/dist/step-shared.js.map +1 -1
- package/dist/template-validation.d.ts.map +1 -1
- package/dist/template-validation.js +5 -5
- package/dist/template-validation.js.map +1 -1
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +2 -1
- package/dist/template.js.map +1 -1
- package/dist/test-helpers.d.ts +91 -0
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +186 -1
- package/dist/test-helpers.js.map +1 -1
- package/dist/tui.d.ts +3 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +1 -1
- package/dist/types.d.ts +36 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow-discovery.d.ts.map +1 -1
- package/dist/workflow-discovery.js +2 -1
- package/dist/workflow-discovery.js.map +1 -1
- package/dist/workflow-executor.d.ts.map +1 -1
- package/dist/workflow-executor.js +23 -9
- package/dist/workflow-executor.js.map +1 -1
- package/dist/workflow-sdk.d.ts.map +1 -1
- package/dist/workflow-sdk.js +14 -8
- package/dist/workflow-sdk.js.map +1 -1
- package/dist/workflow-spec.js +99 -2
- package/dist/workflow-spec.js.map +1 -1
- package/package.json +9 -6
- package/skill-narrative.md +3 -1
- package/skills/pi-workflows/SKILL.md +43 -6
- package/skills/pi-workflows/references/bundled-resources.md +16 -1
- package/templates/items/architecture.md +33 -0
- package/templates/items/conformance.md +26 -0
- package/templates/items/conventions.md +19 -0
- package/templates/items/decisions.md +47 -0
- package/templates/items/domain.md +21 -0
- package/templates/items/features.md +63 -0
- package/templates/items/framework-gaps.md +43 -0
- package/templates/items/issues.md +21 -0
- package/templates/items/layer-plans.md +50 -0
- package/templates/items/project.md +48 -0
- package/templates/items/requirements.md +20 -0
- package/templates/items/research.md +57 -0
- package/templates/items/spec-reviews.md +51 -0
- package/templates/items/tasks.md +29 -0
- package/templates/shared/macros.md +83 -98
- package/templates/shared/render-helpers.md +106 -0
- package/workflows/analyze-existing-project.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +1 -1
- package/workflows/plan-from-requirements.workflow.yaml +2 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Per-item macro for the `project` block (Plan 8 / per-item-macros wave 4).
|
|
3
|
+
|
|
4
|
+
Singleton kind: the project block holds one record per repository (no
|
|
5
|
+
homogeneous items[] array). The "per-item" rendering thus operates on the
|
|
6
|
+
whole block payload as a single record. The whole-block delegator in
|
|
7
|
+
shared/macros.md passes its data argument straight through.
|
|
8
|
+
|
|
9
|
+
No cross-block references — depth parameter accepted for shape-uniformity
|
|
10
|
+
with other per-item macros; no helpers from shared/render-helpers.md are
|
|
11
|
+
needed at present.
|
|
12
|
+
|
|
13
|
+
Empty-array convention:
|
|
14
|
+
Optional list/object fields render their label only when populated. The
|
|
15
|
+
project schema makes `name`, `description`, and `core_value` required and
|
|
16
|
+
the rest optional; bare-required output is the predictable shape for
|
|
17
|
+
minimal records.
|
|
18
|
+
-#}
|
|
19
|
+
|
|
20
|
+
{% macro render_project_item(p, depth=0) %}
|
|
21
|
+
{% if p %}
|
|
22
|
+
## Project
|
|
23
|
+
**{{ p.name }}** — {{ enforceBudget(p.description, "project", "description") }}
|
|
24
|
+
{% if p.core_value %}Core value: {{ enforceBudget(p.core_value, "project", "core_value") }}{% endif %}
|
|
25
|
+
{% if p.vision %}
|
|
26
|
+
Vision: {{ enforceBudget(p.vision, "project", "vision") }}{% endif %}
|
|
27
|
+
{% if p.status %}Status: {{ p.status }}{% endif %}
|
|
28
|
+
{% if p.target_users %}
|
|
29
|
+
Target users: {{ p.target_users | join(", ") }}{% endif %}
|
|
30
|
+
{% if p.constraints %}
|
|
31
|
+
### Constraints
|
|
32
|
+
{% for c in p.constraints %}- [{{ c.type }}] {{ enforceBudget(c.description, "project", "constraints.items.description") }}
|
|
33
|
+
{% endfor %}{% endif %}
|
|
34
|
+
{% if p.scope_boundaries %}
|
|
35
|
+
### Scope
|
|
36
|
+
**In:** {% for s in p.scope_boundaries.in %}{{ s }}{% if not loop.last %}, {% endif %}{% endfor %}
|
|
37
|
+
|
|
38
|
+
**Out:** {% for s in p.scope_boundaries.out %}{{ s }}{% if not loop.last %}, {% endif %}{% endfor %}
|
|
39
|
+
{% endif %}
|
|
40
|
+
{% if p.goals %}
|
|
41
|
+
### Goals
|
|
42
|
+
{% for g in p.goals %}- **{{ g.id }}**: {{ enforceBudget(g.description, "project", "goals.items.description") }}{% if g.success_criteria %} — criteria: {% for sc in g.success_criteria %}{{ enforceBudget(sc, "project", "goals.items.success_criteria.items") }}{% if not loop.last %}; {% endif %}{% endfor %}{% endif %}
|
|
43
|
+
{% endfor %}{% endif %}
|
|
44
|
+
{% if p.repository %}Repository: {{ p.repository }}
|
|
45
|
+
{% endif %}{% if p.stack %}Stack: {{ p.stack | join(", ") }}
|
|
46
|
+
{% endif %}
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% endmacro %}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Per-item macro for the `requirements` block (Plan 8 / per-item-macros wave 4).
|
|
3
|
+
|
|
4
|
+
Block schema: .project/schemas/requirements.schema.json. Each item is one
|
|
5
|
+
requirement inside data.requirements[]. Required: id (^REQ-\d{3,}$),
|
|
6
|
+
description, type, status, priority. Optional: acceptance_criteria,
|
|
7
|
+
source, traces_to (phase/task IDs), depends_on (other requirement IDs).
|
|
8
|
+
|
|
9
|
+
Cross-block reference recursion (traces_to, depends_on) is delegated to
|
|
10
|
+
shared/render-helpers.md — see that file for the depth contract, ambient
|
|
11
|
+
globals, and empty-array convention shared across all per-item macros.
|
|
12
|
+
-#}
|
|
13
|
+
{% from "shared/render-helpers.md" import render_id_list_inline %}
|
|
14
|
+
|
|
15
|
+
{% macro render_requirement(req, depth=0) %}
|
|
16
|
+
{% if req %}- **{{ req.id }}** [{{ req.priority }}] ({{ req.type }}, {{ req.status }}): {{ enforceBudget(req.description, "requirements", "requirements.items.description") }}{% if req.acceptance_criteria %}
|
|
17
|
+
Criteria: {% for ac in req.acceptance_criteria %}{{ enforceBudget(ac, "requirements", "requirements.items.acceptance_criteria.items") }}{% if not loop.last %}; {% endif %}{% endfor %}{% endif %}
|
|
18
|
+
{% if req.source %} Source: {{ req.source }}
|
|
19
|
+
{% endif %}{{ render_id_list_inline("Traces to", req.traces_to, depth) }}{{ render_id_list_inline("Depends on", req.depends_on, depth) }}{% endif %}
|
|
20
|
+
{% endmacro %}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Per-item macro for the `research` block.
|
|
3
|
+
|
|
4
|
+
Block schema: .project/schemas/research.schema.json (Plan 7 / per-item-macros wave 4).
|
|
5
|
+
|
|
6
|
+
Cross-block reference recursion (informs, informed_by, related_research,
|
|
7
|
+
produces_findings, supersedes, superseded_by) is delegated to
|
|
8
|
+
shared/render-helpers.md — see that file for the depth contract, ambient
|
|
9
|
+
globals, and empty-array convention.
|
|
10
|
+
|
|
11
|
+
Embedded substructures:
|
|
12
|
+
`grounding` is an object with dependencies/revisions/external_refs
|
|
13
|
+
string arrays — rendered inline as labelled sub-lists, never recursed.
|
|
14
|
+
`citations[]` carries label/path/lines/url/retrieved_at — labelled
|
|
15
|
+
pointers to source material, not block IDs; rendered inline.
|
|
16
|
+
`stale_conditions[]` and `scope[]` are simple string arrays rendered as
|
|
17
|
+
bullet sub-lists.
|
|
18
|
+
|
|
19
|
+
x-prompt-budget integration:
|
|
20
|
+
The schema annotates `findings_summary`, `question`, `method` with
|
|
21
|
+
`x-prompt-budget`. enforceBudget is wired for these per Plan 5.
|
|
22
|
+
-#}
|
|
23
|
+
{% from "shared/render-helpers.md" import render_id_list_block, render_id_single_block, render_optional_scalar %}
|
|
24
|
+
|
|
25
|
+
{% macro render_research(r, depth=0) %}
|
|
26
|
+
ID: {{ r.id }}
|
|
27
|
+
Title: {{ r.title }}
|
|
28
|
+
Status: {{ r.status }}
|
|
29
|
+
Layer: {{ r.layer }}
|
|
30
|
+
Type: {{ r.type }}
|
|
31
|
+
Created by: {{ r.created_by }}
|
|
32
|
+
Created at: {{ r.created_at }}
|
|
33
|
+
{{ render_optional_scalar("Modified by", r.modified_by) }}{{ render_optional_scalar("Modified at", r.modified_at) }}{{ render_optional_scalar("Conducted by", r.conducted_by) }}{{ render_optional_scalar("Conducted at", r.conducted_at) }}{{ render_optional_scalar("Grounded at", r.grounded_at) }}
|
|
34
|
+
Question:
|
|
35
|
+
{{ enforceBudget(r.question, "research", "research.items.question") }}
|
|
36
|
+
|
|
37
|
+
Method:
|
|
38
|
+
{{ enforceBudget(r.method, "research", "research.items.method") }}
|
|
39
|
+
|
|
40
|
+
{% if r.scope is defined %}Scope:
|
|
41
|
+
{% if r.scope | length > 0 %}{% for s in r.scope %} - {{ s }}
|
|
42
|
+
{% endfor %}{% else %} (none)
|
|
43
|
+
{% endif %}{% endif %}Findings summary:
|
|
44
|
+
{{ enforceBudget(r.findings_summary, "research", "research.items.findings_summary") }}
|
|
45
|
+
|
|
46
|
+
{% if r.findings_document %}Findings document: {{ r.findings_document }}
|
|
47
|
+
{% endif %}{% if r.grounding is defined %}Grounding:
|
|
48
|
+
{% if r.grounding.dependencies is defined %} Dependencies: {% if r.grounding.dependencies | length > 0 %}{% for d in r.grounding.dependencies %}{{ d }}{% if not loop.last %}, {% endif %}{% endfor %}{% else %}(none){% endif %}
|
|
49
|
+
{% endif %}{% if r.grounding.revisions is defined %} Revisions: {% if r.grounding.revisions | length > 0 %}{% for rv in r.grounding.revisions %}{{ rv }}{% if not loop.last %}, {% endif %}{% endfor %}{% else %}(none){% endif %}
|
|
50
|
+
{% endif %}{% if r.grounding.external_refs is defined %} External refs: {% if r.grounding.external_refs | length > 0 %}{% for er in r.grounding.external_refs %}{{ er }}{% if not loop.last %}, {% endif %}{% endfor %}{% else %}(none){% endif %}
|
|
51
|
+
{% endif %}{% endif %}{% if r.stale_conditions is defined %}Stale conditions:
|
|
52
|
+
{% if r.stale_conditions | length > 0 %}{% for sc in r.stale_conditions %} - {{ sc }}
|
|
53
|
+
{% endfor %}{% else %} (none)
|
|
54
|
+
{% endif %}{% endif %}{% if r.citations is defined %}Citations:
|
|
55
|
+
{% if r.citations | length > 0 %}{% for c in r.citations %} - {{ c.label }}{% if c.path %} — path: {{ c.path }}{% if c.lines %}:{{ c.lines }}{% endif %}{% endif %}{% if c.url %} — url: {{ c.url }}{% endif %}{% if c.retrieved_at %} — retrieved_at: {{ c.retrieved_at }}{% endif %}
|
|
56
|
+
{% endfor %}{% else %} (none)
|
|
57
|
+
{% endif %}{% endif %}{{ render_id_list_block("Informs", r.informs, depth) }}{{ render_id_list_block("Informed by", r.informed_by, depth) }}{{ render_id_list_block("Related research", r.related_research, depth) }}{{ render_id_list_block("Produces findings", r.produces_findings, depth) }}{{ render_id_list_block("Supersedes", r.supersedes, depth) }}{{ render_id_single_block("Superseded by", r.superseded_by, depth) }}{% endmacro %}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Per-item macro for the `spec-reviews` block.
|
|
3
|
+
|
|
4
|
+
Block schema: .project/schemas/spec-reviews.schema.json (Plan 7 / per-item-macros wave 4).
|
|
5
|
+
|
|
6
|
+
Findings:
|
|
7
|
+
Each review carries an embedded findings registry (definitions/finding
|
|
8
|
+
in the schema). Findings are rendered inline as a sub-list under the
|
|
9
|
+
review body — they are not block items in their own right and have no
|
|
10
|
+
per-item macro of their own. Each finding's `produces_decision` (when
|
|
11
|
+
present and depth > 0) recurses into the decisions block via
|
|
12
|
+
resolve/render_recursive — that pattern stays inline because it has a
|
|
13
|
+
deeper indent (4 spaces) than the helper convention assumes and is
|
|
14
|
+
embedded in a finding-iteration loop rather than a top-level field.
|
|
15
|
+
`related_findings` on a finding refer to sibling findings within the
|
|
16
|
+
same review by their string ids; they are emitted as bare IDs (no
|
|
17
|
+
recursion — finding IDs are scoped to the review and not in the global
|
|
18
|
+
ID index).
|
|
19
|
+
|
|
20
|
+
Optional review-level scalars (target_revision, reviewer, completed_at,
|
|
21
|
+
clean) and the method body delegate to shared/render-helpers.md
|
|
22
|
+
render_optional_scalar where the shape matches.
|
|
23
|
+
-#}
|
|
24
|
+
{% from "shared/render-helpers.md" import render_optional_scalar %}
|
|
25
|
+
|
|
26
|
+
{% macro render_spec_review(rev, depth=0) %}
|
|
27
|
+
ID: {{ rev.id }}
|
|
28
|
+
Target: {{ rev.target }}
|
|
29
|
+
Status: {{ rev.status }}
|
|
30
|
+
Created by: {{ rev.created_by }}
|
|
31
|
+
Created at: {{ rev.created_at }}
|
|
32
|
+
{{ render_optional_scalar("Target revision", rev.target_revision) }}{{ render_optional_scalar("Reviewer", rev.reviewer) }}{{ render_optional_scalar("Completed at", rev.completed_at) }}{% if rev.clean is defined %}Clean: {{ rev.clean }}
|
|
33
|
+
{% endif %}{% if rev.method %}Method:
|
|
34
|
+
{{ enforceBudget(rev.method, "spec-reviews", "reviews.items.method") }}
|
|
35
|
+
|
|
36
|
+
{% endif %}{% if rev.scope is defined %}Scope:
|
|
37
|
+
{% if rev.scope | length > 0 %}{% for s in rev.scope %} - {{ s }}
|
|
38
|
+
{% endfor %}{% else %} (none)
|
|
39
|
+
{% endif %}{% endif %}Findings:
|
|
40
|
+
{% if rev.findings and rev.findings | length > 0 %}{% for f in rev.findings %} - {{ f.id }} [{{ f.severity }}/{{ f.category }}/{{ f.state }}] reporter={{ f.reporter }} at={{ f.created_at }}
|
|
41
|
+
Description: {{ enforceBudget(f.description, "spec-reviews", "reviews.items.findings.items.description") }}
|
|
42
|
+
{% if f.evidence %} Evidence: {{ f.evidence }}
|
|
43
|
+
{% endif %}{% if f.location %} Location: {{ f.location }}
|
|
44
|
+
{% endif %}{% if f.resolution %} Resolution: {{ f.resolution }}
|
|
45
|
+
{% endif %}{% if f.resolved_by %} Resolved by: {{ f.resolved_by }}
|
|
46
|
+
{% endif %}{% if f.resolved_at %} Resolved at: {{ f.resolved_at }}
|
|
47
|
+
{% endif %}{% if f.produces_decision %} Produces decision: {% if depth > 0 %}{% set loc = resolve(f.produces_decision) %}{% if loc %}
|
|
48
|
+
{{ render_recursive(loc, depth - 1) }}{% else %}{{ f.produces_decision }}{% endif %}{% else %}{{ f.produces_decision }}{% endif %}
|
|
49
|
+
{% endif %}{% if f.related_findings is defined %} Related findings: {% if f.related_findings | length > 0 %}{% for rf in f.related_findings %}{{ rf }}{% if not loop.last %}, {% endif %}{% endfor %}{% else %}(none){% endif %}
|
|
50
|
+
{% endif %}{% endfor %}{% else %} (none)
|
|
51
|
+
{% endif %}{% endmacro %}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Per-item macro for the `tasks` block (Plan 8 / per-item-macros wave 4).
|
|
3
|
+
|
|
4
|
+
Block schema: .project/schemas/tasks.schema.json. Each item is one task
|
|
5
|
+
inside data.tasks[]. Required: id (^TASK-\d{3,}$), description, status.
|
|
6
|
+
Optional: phase (string|integer), files, acceptance_criteria, depends_on
|
|
7
|
+
(other task IDs), assigned_agent, verification (verification ID), notes.
|
|
8
|
+
|
|
9
|
+
When status === "completed", schema additionally requires verification
|
|
10
|
+
(the if/then conditional in the schema).
|
|
11
|
+
|
|
12
|
+
Cross-block reference recursion (depends_on, verification) is delegated to
|
|
13
|
+
shared/render-helpers.md — see that file for the depth contract, ambient
|
|
14
|
+
globals, and empty-array convention. verification is rendered as a single
|
|
15
|
+
inline reference (one ID) rather than a list.
|
|
16
|
+
|
|
17
|
+
Files field:
|
|
18
|
+
files[] is a list of code paths, not project-block IDs. Rendered inline
|
|
19
|
+
as a comma-separated list; never recursed.
|
|
20
|
+
-#}
|
|
21
|
+
{% from "shared/render-helpers.md" import render_id_list_inline, render_id_single_inline %}
|
|
22
|
+
|
|
23
|
+
{% macro render_task(t, depth=0) %}
|
|
24
|
+
{% if t %}- **{{ t.id }}** [{{ t.status }}]: {{ enforceBudget(t.description, "tasks", "tasks.items.description") }}{% if t.phase %} (phase {{ t.phase }}){% endif %}{% if t.files %} — files: {{ t.files | join(", ") }}{% endif %}
|
|
25
|
+
{% if t.acceptance_criteria %} Criteria: {% for ac in t.acceptance_criteria %}{{ enforceBudget(ac, "tasks", "tasks.items.acceptance_criteria.items") }}{% if not loop.last %}; {% endif %}{% endfor %}
|
|
26
|
+
{% endif %}{% if t.assigned_agent %} Assigned agent: {{ t.assigned_agent }}
|
|
27
|
+
{% endif %}{% if t.notes %} Notes: {{ enforceBudget(t.notes, "tasks", "tasks.items.notes") }}
|
|
28
|
+
{% endif %}{{ render_id_list_inline("Depends on", t.depends_on, depth) }}{{ render_id_single_inline("Verification", t.verification, depth) }}{% endif %}
|
|
29
|
+
{% endmacro %}
|
|
@@ -1,109 +1,104 @@
|
|
|
1
|
-
{#- Block rendering macros —
|
|
2
|
-
Import via: {% from "shared/macros.md" import render_project, render_architecture %}
|
|
3
|
-
Each macro: null guard, markdown heading, field rendering. Missing data renders nothing. -#}
|
|
1
|
+
{#- Block rendering macros — whole-block forms as derived views over per-item macros.
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
{% if data.core_value %}Core value: {{ data.core_value }}{% endif %}
|
|
10
|
-
{% if data.vision %}
|
|
11
|
-
Vision: {{ data.vision }}{% endif %}
|
|
12
|
-
{% if data.status %}Status: {{ data.status }}{% endif %}
|
|
13
|
-
{% if data.target_users %}
|
|
14
|
-
Target users: {{ data.target_users | join(", ") }}{% endif %}
|
|
15
|
-
{% if data.constraints %}
|
|
16
|
-
### Constraints
|
|
17
|
-
{% for c in data.constraints %}- [{{ c.type }}] {{ c.description }}
|
|
18
|
-
{% endfor %}{% endif %}
|
|
19
|
-
{% if data.scope_boundaries %}
|
|
20
|
-
### Scope
|
|
21
|
-
**In:** {% for s in data.scope_boundaries.in %}{{ s }}{% if not loop.last %}, {% endif %}{% endfor %}
|
|
3
|
+
Plan 8 (Wave 4) refactor: each whole-block macro that has a per-item sibling
|
|
4
|
+
in templates/items/<kind>.md delegates to that per-item macro by mapping
|
|
5
|
+
over the items array. Per-item macros own field rendering; whole-block macros
|
|
6
|
+
own the surrounding heading and the null/empty guard.
|
|
22
7
|
|
|
23
|
-
|
|
24
|
-
{%
|
|
25
|
-
{%
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
v0.24.2 follow-up (Item 3 of 2026-05-02 residual-debt patch): the
|
|
9
|
+
repeated `{% if data and data.<key> [and data.<key> | length > 0] %}` +
|
|
10
|
+
`## <Heading>` + `{% for x in data.<key> %}{{ render_<kind>_item(x) }}
|
|
11
|
+
{% endfor %}` shape that previously lived inline in render_requirements,
|
|
12
|
+
render_domain, render_tasks, and render_issues now routes through the
|
|
13
|
+
`render_whole_block_truthy` / `render_whole_block_nonempty` scaffolds in
|
|
14
|
+
shared/render-helpers.md. The guard / heading / loop chassis lives once
|
|
15
|
+
in render-helpers; each whole-block macro here is the per-kind binding
|
|
16
|
+
(heading text, items slice, per-item macro inside the `{% call %}` body).
|
|
17
|
+
Output stays byte-identical to the prior inline shape — the two
|
|
18
|
+
truthy-vs-nonempty helpers preserve the empty-array surface each
|
|
19
|
+
delegator originally shipped (tasks/issues/domain emit `## <Heading>`
|
|
20
|
+
with empty array; requirements emits nothing — both regimes are kept
|
|
21
|
+
explicitly so existing consumers see no diff).
|
|
22
|
+
|
|
23
|
+
Imports for downstream consumers (unchanged signatures):
|
|
24
|
+
{% from "shared/macros.md" import render_project, render_architecture,
|
|
25
|
+
render_requirements, render_conformance,
|
|
26
|
+
render_domain, render_tasks, render_issues,
|
|
27
|
+
render_conventions,
|
|
28
|
+
render_exploration, render_exploration_full %}
|
|
29
|
+
|
|
30
|
+
Each macro:
|
|
31
|
+
- null/empty guard via the helper or local conditional,
|
|
32
|
+
- markdown heading (via the helper for list kinds; inline for the
|
|
33
|
+
singletons and the heading-bearing block-level shapes),
|
|
34
|
+
- delegation loop calling the per-item macro per item.
|
|
35
|
+
|
|
36
|
+
Retired in Plan 8:
|
|
37
|
+
- render_decisions(data) — legacy whole-block decisions macro deleted.
|
|
38
|
+
The new lifecycle decisions block has its own per-item macro
|
|
39
|
+
(templates/items/decisions.md ships render_decision + alias
|
|
40
|
+
render_decisions). The legacy macro rendered fields (decision,
|
|
41
|
+
rationale) that no longer match the current decisions.schema.json.
|
|
42
|
+
- render_gap(gap) — legacy single-item validation-concept macro deleted.
|
|
43
|
+
The framework-gaps.json block uses Plan 7's render_framework_gap in
|
|
44
|
+
templates/items/framework-gaps.md.
|
|
45
|
+
|
|
46
|
+
Retained as-is:
|
|
47
|
+
- render_exploration(exploration) and render_exploration_full(exploration)
|
|
48
|
+
are workflow-step output formatters (rendering exploration payloads
|
|
49
|
+
emitted by exploration agents), not project-block renderers. They do
|
|
50
|
+
not fit the per-item refactor pattern and stay unchanged. -#}
|
|
51
|
+
|
|
52
|
+
{% from "shared/render-helpers.md" import render_whole_block_truthy, render_whole_block_nonempty %}
|
|
53
|
+
|
|
54
|
+
{% from "items/project.md" import render_project_item %}
|
|
55
|
+
{% macro render_project(data) %}
|
|
56
|
+
{% if data %}{{ render_project_item(data) }}{% endif %}
|
|
30
57
|
{% endmacro %}
|
|
31
58
|
|
|
59
|
+
{% from "items/architecture.md" import render_architecture_item %}
|
|
32
60
|
{% macro render_architecture(data) %}
|
|
33
|
-
{% if data %}
|
|
34
|
-
## Architecture
|
|
35
|
-
{% if data.overview %}{{ data.overview }}
|
|
36
|
-
{% endif %}
|
|
37
|
-
{% if data.modules %}
|
|
38
|
-
### Modules
|
|
39
|
-
{% for m in data.modules %}- **{{ m.name }}** (`{{ m.file }}`{% if m.lines %}, {{ m.lines }} lines{% endif %}): {{ m.responsibility }}{% if m.dependencies %} — deps: {{ m.dependencies | join(", ") }}{% endif %}
|
|
40
|
-
{% endfor %}{% endif %}
|
|
41
|
-
{% if data.patterns %}
|
|
42
|
-
### Patterns
|
|
43
|
-
{% for p in data.patterns %}- **{{ p.name }}**: {{ p.description }}{% if p.used_in %} — used in: {{ p.used_in | join(", ") }}{% endif %}
|
|
44
|
-
{% endfor %}{% endif %}
|
|
45
|
-
{% if data.boundaries %}
|
|
46
|
-
### Boundaries
|
|
47
|
-
{% for b in data.boundaries %}- {{ b }}
|
|
48
|
-
{% endfor %}{% endif %}
|
|
49
|
-
{% endif %}
|
|
61
|
+
{% if data %}{{ render_architecture_item(data) }}{% endif %}
|
|
50
62
|
{% endmacro %}
|
|
51
63
|
|
|
52
|
-
{%
|
|
53
|
-
{%
|
|
54
|
-
## Requirements
|
|
55
|
-
{% for r in data.requirements %}- **{{ r.id }}** [{{ r.priority }}] ({{ r.type }}, {{ r.status }}): {{ r.description }}{% if r.acceptance_criteria %}
|
|
56
|
-
Criteria: {{ r.acceptance_criteria | join("; ") }}{% endif %}
|
|
57
|
-
{% endfor %}
|
|
58
|
-
{% endif %}
|
|
59
|
-
{% endmacro %}
|
|
64
|
+
{% from "items/requirements.md" import render_requirement %}
|
|
65
|
+
{% macro render_requirements(data) %}{% call(req) render_whole_block_nonempty("Requirements", data.requirements if data else None) %}{{ render_requirement(req) }}{% endcall %}{% endmacro %}
|
|
60
66
|
|
|
67
|
+
{% from "items/conformance.md" import render_conformance_principle %}
|
|
61
68
|
{% macro render_conformance(data) %}
|
|
62
69
|
{% if data and data.principles %}
|
|
63
70
|
## Conformance Reference
|
|
64
71
|
{% if data.name %}**{{ data.name }}**{% endif %}
|
|
65
|
-
{% for p in data.principles %}
|
|
66
|
-
### {{ p.id }}: {{ p.name }}
|
|
67
|
-
{% if p.description %}{{ p.description }}{% endif %}
|
|
68
|
-
{% for r in p.rules %}- **{{ r.id }}**: {{ r.rule }}{% if r.severity %} [{{ r.severity }}]{% endif %}{% if r.check_method %} — check: {{ r.check_method }}{% endif %}
|
|
69
|
-
{% if r.anti_patterns %} Anti-patterns: {{ r.anti_patterns | join("; ") }}
|
|
70
|
-
{% endif %}{% endfor %}
|
|
71
|
-
{% endfor %}
|
|
72
|
+
{% for p in data.principles %}{{ render_conformance_principle(p) }}{% endfor %}
|
|
72
73
|
{% endif %}
|
|
73
74
|
{% endmacro %}
|
|
74
75
|
|
|
75
|
-
{%
|
|
76
|
-
{%
|
|
77
|
-
## Domain Knowledge
|
|
78
|
-
{% for e in data.entries %}- **{{ e.id }}** [{{ e.category }}]: {{ e.title }}
|
|
79
|
-
{{ e.content }}{% if e.tags %} — tags: {{ e.tags | join(", ") }}{% endif %}
|
|
80
|
-
{% endfor %}
|
|
81
|
-
{% endif %}
|
|
82
|
-
{% endmacro %}
|
|
76
|
+
{% from "items/domain.md" import render_domain_entry %}
|
|
77
|
+
{% macro render_domain(data) %}{% call(e) render_whole_block_truthy("Domain Knowledge", data.entries if data else None) %}{{ render_domain_entry(e) }}{% endcall %}{% endmacro %}
|
|
83
78
|
|
|
84
|
-
{%
|
|
85
|
-
{%
|
|
86
|
-
## Decisions
|
|
87
|
-
{% for d in data.decisions %}- **{{ d.id }}** ({{ d.status }}): {{ d.decision }}
|
|
88
|
-
Rationale: {{ d.rationale }}{% if d.context %} — context: {{ d.context }}{% endif %}
|
|
89
|
-
{% endfor %}
|
|
90
|
-
{% endif %}
|
|
91
|
-
{% endmacro %}
|
|
79
|
+
{% from "items/tasks.md" import render_task %}
|
|
80
|
+
{% macro render_tasks(data) %}{% call(t) render_whole_block_truthy("Tasks", data.tasks if data else None) %}{{ render_task(t) }}{% endcall %}{% endmacro %}
|
|
92
81
|
|
|
93
|
-
{%
|
|
94
|
-
{%
|
|
95
|
-
## Tasks
|
|
96
|
-
{% for t in data.tasks %}- **{{ t.id }}** [{{ t.status }}]: {{ t.description }}{% if t.phase %} (phase {{ t.phase }}){% endif %}{% if t.files %} — files: {{ t.files | join(", ") }}{% endif %}
|
|
97
|
-
{% endfor %}
|
|
98
|
-
{% endif %}
|
|
99
|
-
{% endmacro %}
|
|
82
|
+
{% from "items/issues.md" import render_issue %}
|
|
83
|
+
{% macro render_issues(data) %}{% call(i) render_whole_block_truthy("Issues", data.issues if data else None) %}{{ render_issue(i) }}{% endcall %}{% endmacro %}
|
|
100
84
|
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
85
|
+
{#- Authored fresh in Plan 8: render_conventions did not previously exist in
|
|
86
|
+
macros.md despite README references implying it did. The block-level
|
|
87
|
+
header surfaces test_conventions, lint_command, and lint_scope (all
|
|
88
|
+
optional) before iterating rules through render_convention.
|
|
89
|
+
|
|
90
|
+
Block schema: .project/schemas/conventions.schema.json. Required: rules.
|
|
91
|
+
Optional: test_conventions { runner_command, file_pattern }, lint_command,
|
|
92
|
+
lint_scope. -#}
|
|
93
|
+
{% from "items/conventions.md" import render_convention %}
|
|
94
|
+
{% macro render_conventions(data) %}
|
|
95
|
+
{% if data and data.rules %}
|
|
96
|
+
## Conventions
|
|
97
|
+
{% if data.test_conventions %}
|
|
98
|
+
**Tests:** `{{ data.test_conventions.runner_command }}` (pattern: `{{ data.test_conventions.file_pattern }}`)
|
|
99
|
+
{% endif %}{% if data.lint_command %}**Lint:** `{{ data.lint_command }}`{% if data.lint_scope %} (scope: {{ data.lint_scope }}){% endif %}
|
|
100
|
+
{% endif %}
|
|
101
|
+
{% for rule in data.rules %}{{ render_convention(rule) }}{% endfor %}
|
|
107
102
|
{% endif %}
|
|
108
103
|
{% endmacro %}
|
|
109
104
|
|
|
@@ -140,13 +135,3 @@ Target users: {{ data.target_users | join(", ") }}{% endif %}
|
|
|
140
135
|
{% endfor %}{% endif %}
|
|
141
136
|
{% endif %}
|
|
142
137
|
{% endmacro %}
|
|
143
|
-
|
|
144
|
-
{% macro render_gap(gap) %}
|
|
145
|
-
{% if gap %}
|
|
146
|
-
**ID:** {{ gap.id }}
|
|
147
|
-
**Description:** {{ gap.description }}
|
|
148
|
-
**Category:** {{ gap.category | default("unspecified") }}
|
|
149
|
-
**Priority:** {{ gap.priority | default("unspecified") }}
|
|
150
|
-
{% if gap.details %}**Details:** {{ gap.details }}{% endif %}
|
|
151
|
-
{% endif %}
|
|
152
|
-
{% endmacro %}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Shared rendering helpers for per-item macros.
|
|
3
|
+
|
|
4
|
+
Captures repeated micro-patterns that previously lived inline in every
|
|
5
|
+
per-item macro file under `items/*.md`. The aim is one definition per
|
|
6
|
+
pattern, consumed by every per-item macro that uses it.
|
|
7
|
+
|
|
8
|
+
Helpers exported:
|
|
9
|
+
render_id_list_block(label, ids, depth)
|
|
10
|
+
render_id_single_block(label, id, depth)
|
|
11
|
+
render_id_list_inline(label, ids, depth)
|
|
12
|
+
render_id_single_inline(label, id, depth)
|
|
13
|
+
render_optional_scalar(label, value)
|
|
14
|
+
|
|
15
|
+
Output convention:
|
|
16
|
+
Block-style helpers emit `<Label>:\n` followed by ` - <id>\n` per entry
|
|
17
|
+
(or ` (none)\n` for present-but-empty arrays). Absent (undefined)
|
|
18
|
+
arrays emit nothing.
|
|
19
|
+
|
|
20
|
+
Inline-style helpers emit ` <Label>: <id>[, <id>...]\n` (or `(none)\n`
|
|
21
|
+
for empty). The two-space indent prefix is part of the inline shape used
|
|
22
|
+
by domain.related_requirements, requirements.traces_to/depends_on, and
|
|
23
|
+
tasks.depends_on/verification — it is the indent under a parent list
|
|
24
|
+
bullet, not arbitrary padding.
|
|
25
|
+
|
|
26
|
+
Depth contract:
|
|
27
|
+
Each helper that takes `depth` honours the same recursion budget as the
|
|
28
|
+
per-item macros: depth <= 0 emits bare ID text; depth > 0 calls resolve
|
|
29
|
+
+ render_recursive (ambient Nunjucks globals registered by compileAgent
|
|
30
|
+
in @davidorex/pi-jit-agents) and falls back to bare-ID on resolve miss.
|
|
31
|
+
Cycles → `[cycle: <id>]` and missing per-item macros → `[unrendered:
|
|
32
|
+
<kind>/<id>]` are produced inside render_recursive itself, not here.
|
|
33
|
+
|
|
34
|
+
resolve(id) and render_recursive(loc, depth) are env-level globals
|
|
35
|
+
available to imported macros without re-import.
|
|
36
|
+
|
|
37
|
+
Empty-array convention:
|
|
38
|
+
`is defined` discriminates absent vs present. Present-but-empty arrays
|
|
39
|
+
render the label with `(none)`; absent fields render nothing. This
|
|
40
|
+
preserves the per-item macro contract documented in items/decisions.md
|
|
41
|
+
and mirrored across every per-item macro.
|
|
42
|
+
|
|
43
|
+
Output equivalence:
|
|
44
|
+
The helper bodies are byte-identical to the patterns they replace. The
|
|
45
|
+
existing render-*.test.ts assertions are the regression net. If a
|
|
46
|
+
helper drifts from the inline pattern's output, those tests break.
|
|
47
|
+
-#}
|
|
48
|
+
|
|
49
|
+
{% macro render_id_list_block(label, ids, depth) %}{% if ids is defined %}{{ label }}:
|
|
50
|
+
{% if ids | length > 0 %}{% for id in ids %}{% if depth > 0 %}{% set loc = resolve(id) %}{% if loc %}{{ render_recursive(loc, depth - 1) }}
|
|
51
|
+
{% else %} - {{ id }}
|
|
52
|
+
{% endif %}{% else %} - {{ id }}
|
|
53
|
+
{% endif %}{% endfor %}{% else %} (none)
|
|
54
|
+
{% endif %}{% endif %}{% endmacro %}
|
|
55
|
+
|
|
56
|
+
{% macro render_id_single_block(label, id, depth) %}{% if id is defined and id %}{{ label }}:
|
|
57
|
+
{% if depth > 0 %}{% set loc = resolve(id) %}{% if loc %}{{ render_recursive(loc, depth - 1) }}
|
|
58
|
+
{% else %} - {{ id }}
|
|
59
|
+
{% endif %}{% else %} - {{ id }}
|
|
60
|
+
{% endif %}{% endif %}{% endmacro %}
|
|
61
|
+
|
|
62
|
+
{% macro render_id_list_inline(label, ids, depth) %}{% if ids is defined %} {{ label }}: {% if ids | length > 0 %}{% for id in ids %}{% if depth > 0 %}{% set loc = resolve(id) %}{% if loc %}{{ render_recursive(loc, depth - 1) }}{% else %}{{ id }}{% endif %}{% else %}{{ id }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% else %}(none){% endif %}
|
|
63
|
+
{% endif %}{% endmacro %}
|
|
64
|
+
|
|
65
|
+
{% macro render_id_single_inline(label, id, depth) %}{% if id %} {{ label }}: {% if depth > 0 %}{% set loc = resolve(id) %}{% if loc %}{{ render_recursive(loc, depth - 1) }}{% else %}{{ id }}{% endif %}{% else %}{{ id }}{% endif %}
|
|
66
|
+
{% endif %}{% endmacro %}
|
|
67
|
+
|
|
68
|
+
{% macro render_optional_scalar(label, value) %}{% if value %}{{ label }}: {{ value }}
|
|
69
|
+
{% endif %}{% endmacro %}
|
|
70
|
+
|
|
71
|
+
{#-
|
|
72
|
+
Whole-block list scaffold (Plan 8 follow-up).
|
|
73
|
+
|
|
74
|
+
Centralises the truthy-guard + heading + per-item iteration shape that
|
|
75
|
+
previously repeated across the whole-block delegators in shared/macros.md
|
|
76
|
+
for `render_domain`, `render_tasks`, and `render_issues`. Each delegator
|
|
77
|
+
now reduces to one `{% call %}` block that supplies the per-item macro as
|
|
78
|
+
its body — the heading-and-loop chassis lives here once.
|
|
79
|
+
|
|
80
|
+
Two variants encode the two empty-handling regimes the legacy delegators
|
|
81
|
+
shipped, kept distinct so output stays byte-identical to the originals:
|
|
82
|
+
|
|
83
|
+
`render_whole_block_truthy(heading, items)`
|
|
84
|
+
Heading-and-loop fire whenever `items` is truthy (matches the
|
|
85
|
+
legacy `{% if data and data.<key> %}` guard used by tasks, issues,
|
|
86
|
+
domain). Empty arrays still emit the heading because `[]` is truthy
|
|
87
|
+
in Nunjucks — that surface is preserved deliberately.
|
|
88
|
+
|
|
89
|
+
`render_whole_block_nonempty(heading, items)`
|
|
90
|
+
Heading-and-loop fire only when `items | length > 0` (matches the
|
|
91
|
+
legacy `{% if data and data.requirements and data.requirements |
|
|
92
|
+
length > 0 %}` guard used by requirements). Empty arrays produce
|
|
93
|
+
nothing.
|
|
94
|
+
|
|
95
|
+
In both, the caller body (`{{ caller(item) }}`) is invoked once per item
|
|
96
|
+
with the item bound to the call-block parameter.
|
|
97
|
+
-#}
|
|
98
|
+
{% macro render_whole_block_truthy(heading, items) %}{% if items %}
|
|
99
|
+
## {{ heading }}
|
|
100
|
+
{% for item in items %}{{ caller(item) }}{% endfor %}
|
|
101
|
+
{% endif %}{% endmacro %}
|
|
102
|
+
|
|
103
|
+
{% macro render_whole_block_nonempty(heading, items) %}{% if items and items | length > 0 %}
|
|
104
|
+
## {{ heading }}
|
|
105
|
+
{% for item in items %}{{ caller(item) }}{% endfor %}
|
|
106
|
+
{% endif %}{% endmacro %}
|
|
@@ -85,4 +85,4 @@ completion:
|
|
|
85
85
|
**Architecture:** ${{ steps.infer-architecture.output.modules | length }} modules, ${{ steps.infer-architecture.output.patterns | length }} patterns
|
|
86
86
|
**Gaps:** ${{ steps.identify-issues.output.issues | length }} identified
|
|
87
87
|
|
|
88
|
-
Use `/
|
|
88
|
+
Use `/context status` to see the full state. Use `/context validate` to check cross-block references.
|
|
@@ -78,4 +78,4 @@ completion:
|
|
|
78
78
|
**Architecture:** ${{ steps.design-architecture.output.modules | length }} modules
|
|
79
79
|
**Plan:** phases and tasks created
|
|
80
80
|
|
|
81
|
-
Use `/
|
|
81
|
+
Use `/context status` to see the full state. Use `/context validate` to check cross-block references.
|
|
@@ -44,5 +44,5 @@ completion:
|
|
|
44
44
|
template: |
|
|
45
45
|
Plan created from requirements. Tasks written to .project/tasks.json.
|
|
46
46
|
|
|
47
|
-
Use `/
|
|
48
|
-
Use `/
|
|
47
|
+
Use `/context status` to see the phase and task breakdown.
|
|
48
|
+
Use `/context validate` to check cross-block references.
|