pirl-framework-cli 0.0.0-stage → 1.0.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 (92) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +21 -0
  3. package/README.md +265 -2
  4. package/bundled-framework/files/.agents/evals/layer_record.py +438 -0
  5. package/bundled-framework/files/.agents/evals/tests/test_layer_record.py +264 -0
  6. package/bundled-framework/files/.agents/evals/tests/test_skills.py +210 -0
  7. package/bundled-framework/files/.agents/evals/tests/test_validate_layer_artifact.py +214 -0
  8. package/bundled-framework/files/.agents/evals/validate_layer_artifact.py +415 -0
  9. package/bundled-framework/files/.agents/instructions/implement.instructions.md +343 -0
  10. package/bundled-framework/files/.agents/instructions/layered-task-execution.instructions.md +370 -0
  11. package/bundled-framework/files/.agents/instructions/layout-verification.instructions.md +107 -0
  12. package/bundled-framework/files/.agents/instructions/plan.instructions.md +292 -0
  13. package/bundled-framework/files/.agents/instructions/review.instructions.md +236 -0
  14. package/bundled-framework/files/.agents/instructions/self-evaluation.instructions.md +146 -0
  15. package/bundled-framework/files/.agents/instructions/standalone-and-maintenance.instructions.md +230 -0
  16. package/bundled-framework/files/.agents/roles/backend-developer.role.md +45 -0
  17. package/bundled-framework/files/.agents/roles/business-analyst.role.md +55 -0
  18. package/bundled-framework/files/.agents/roles/code-analyst.role.md +63 -0
  19. package/bundled-framework/files/.agents/roles/code-reviewer.role.md +46 -0
  20. package/bundled-framework/files/.agents/roles/frontend-developer.role.md +51 -0
  21. package/bundled-framework/files/.agents/roles/process-auditor.role.md +62 -0
  22. package/bundled-framework/files/.agents/roles/solution-architect.role.md +53 -0
  23. package/bundled-framework/files/.agents/roles/technical-writer.role.md +68 -0
  24. package/bundled-framework/files/.agents/roles/ux-designer.role.md +46 -0
  25. package/bundled-framework/files/.skills/pirl-implement/SKILL.md +115 -0
  26. package/bundled-framework/files/.skills/pirl-implement/assets/implementation-record-template.md +22 -0
  27. package/bundled-framework/files/.skills/pirl-implement/references/implementation-record-format.md +41 -0
  28. package/bundled-framework/files/.skills/pirl-implement/scripts/add_implementation_record.py +58 -0
  29. package/bundled-framework/files/.skills/pirl-implement/scripts/new_layout_run.py +51 -0
  30. package/bundled-framework/files/.skills/pirl-plan/SKILL.md +100 -0
  31. package/bundled-framework/files/.skills/pirl-plan/assets/plan-template.md +48 -0
  32. package/bundled-framework/files/.skills/pirl-plan/references/plan-format.md +65 -0
  33. package/bundled-framework/files/.skills/pirl-plan/scripts/new_plan.py +93 -0
  34. package/bundled-framework/files/.skills/pirl-review/SKILL.md +106 -0
  35. package/bundled-framework/files/.skills/pirl-review/assets/review-template.md +92 -0
  36. package/bundled-framework/files/.skills/pirl-review/references/review-format.md +70 -0
  37. package/bundled-framework/files/.skills/pirl-review/scripts/new_review.py +83 -0
  38. package/bundled-framework/files/AGENTS.md +303 -0
  39. package/bundled-framework/files/CONTEXT.md +187 -0
  40. package/bundled-framework/files/LICENSE +21 -0
  41. package/bundled-framework/files/README.md +170 -0
  42. package/bundled-framework/files/gitignore.pirl-bundled +12 -0
  43. package/bundled-framework/manifest.json +45 -0
  44. package/dist/bin/main.js +128 -0
  45. package/dist/bin/pirl.js +6 -0
  46. package/dist/commands/dispatch.js +57 -0
  47. package/dist/commands/registry.js +39 -0
  48. package/dist/core/clipboard.js +70 -0
  49. package/dist/core/config.js +73 -0
  50. package/dist/core/exec.js +32 -0
  51. package/dist/core/paths.js +34 -0
  52. package/dist/core/targetRepo.js +22 -0
  53. package/dist/doctor/doctor.js +95 -0
  54. package/dist/engine/agentMerge.js +71 -0
  55. package/dist/engine/agents.js +67 -0
  56. package/dist/engine/attachments.js +251 -0
  57. package/dist/engine/claudeQuota.js +62 -0
  58. package/dist/engine/claudeRunner.js +118 -0
  59. package/dist/engine/claudeStream.js +205 -0
  60. package/dist/engine/hubData.js +176 -0
  61. package/dist/engine/permissions.js +67 -0
  62. package/dist/engine/pricing.js +24 -0
  63. package/dist/engine/protocol.js +98 -0
  64. package/dist/engine/run.js +527 -0
  65. package/dist/engine/runStore.js +67 -0
  66. package/dist/engine/types.js +3 -0
  67. package/dist/hub/framework.js +13 -0
  68. package/dist/hub/hub.js +125 -0
  69. package/dist/hub/services.js +38 -0
  70. package/dist/hub/update.js +261 -0
  71. package/dist/providers/claude.js +62 -0
  72. package/dist/providers/index.js +17 -0
  73. package/dist/providers/types.js +1 -0
  74. package/dist/ui/App.js +849 -0
  75. package/dist/ui/browse.js +144 -0
  76. package/dist/ui/components/Header.js +54 -0
  77. package/dist/ui/components/Input.js +76 -0
  78. package/dist/ui/components/Lines.js +50 -0
  79. package/dist/ui/components/Panels.js +73 -0
  80. package/dist/ui/content.js +225 -0
  81. package/dist/ui/frameworkConsole.js +541 -0
  82. package/dist/ui/inputEditor.js +86 -0
  83. package/dist/ui/layout.js +36 -0
  84. package/dist/ui/lines.js +15 -0
  85. package/dist/ui/listOverlay.js +97 -0
  86. package/dist/ui/runView.js +305 -0
  87. package/dist/ui/session.js +1 -0
  88. package/dist/ui/terminalInput.js +45 -0
  89. package/dist/ui/text.js +58 -0
  90. package/dist/ui/theme.js +139 -0
  91. package/dist/ui/ticker.js +48 -0
  92. package/package.json +67 -4
@@ -0,0 +1,48 @@
1
+ ---
2
+ schema: pirl-plan/v1
3
+ content_language: en
4
+ plan_set: {{plan_set}}
5
+ task_id: {{task_id}}
6
+ title: "<Task name>"
7
+ layer: implement
8
+ status: planned
9
+ depends_on: []
10
+ created_at: "{{created_at}}"
11
+ target_repo: "<Path of the target repository, relative to the Hub root or absolute; . for the Hub itself>"
12
+ source_request: "<Short version of the user's request>"
13
+ planning_roles: [<planning-role>]
14
+ implementation_roles: [<implementation-role>]
15
+ verification_roles: [<verification-role>]
16
+ ---
17
+
18
+ # {{task_id}}: <Task name>
19
+
20
+ ## What to do
21
+
22
+ - REQ-01 | Source: <Request or file/symbol read> | Requirement: <Business behavior to achieve>
23
+ - Scope: <Work to be done; write exclusions only when needed to avoid misunderstanding>.
24
+ - Allowed paths: `<specific paths of source, documentation and tests under FUT, if any>`.
25
+ - Standalone task outcome: <Output that can be accepted while later/parallel tasks are not yet implemented>.
26
+ <If the specification has build/test/release goals: one line saying they belong to the user's build/test/release stage>.
27
+
28
+ ## How to do it
29
+
30
+ 1. <ROLE-01: which file/symbol to change, how and with what outcome; add steps when there is a real dependency>.
31
+ <If there are tests: ROLE-01 writes/updates tests under FUT for which REQ and which affected functionality, and runs exactly those tests (file/class/name)>.
32
+ 2. <ROLE-02: after ROLE-01, reasons on the diff/source to conclude which QGs; runs no commands/tests>.
33
+
34
+ <If depends_on is set: the concrete output to receive from each prerequisite task>.
35
+ <If end-to-end criteria are assigned to another task: the owning task and the integration part to verify; not a QG of this task>.
36
+ <Input conditions or decisions to stop at, if any>.
37
+ Next step: the user names this Plan file for Implement once the Plan content
38
+ fixes are complete; Implement carries out only this task within the scope above.
39
+
40
+ ## Participating roles
41
+
42
+ - ROLE-01 | Type: implementation | Role: <implementation-role> | Responsible for: <Concrete work> | Input: <Information or dependent results> | Done when: <Source/documentation to deliver>
43
+ - ROLE-02 | Type: verification | Role: <verification-role> | Responsible for: <QGs and the questions to answer> | Input: <ROLE-01's diff/source> | Done when: <PASS/FAIL/BLOCKED conclusion for each QG by reasoning with sources>
44
+ - Planning | Roles: <the roles in planning_roles> | Execution: <separate agent, or the coordinator self-performing with the reason delegation was not possible; per role when they differ>
45
+
46
+ ## Required quality gates
47
+
48
+ - QG-01 | Requirement: REQ-01 | Required logic: <Situation → where it is handled → outcome/state and failure branches; provable by reasoning on the source with the prerequisite inputs and this task's changes, without tests/builds/runs or later/parallel tasks>
@@ -0,0 +1,65 @@
1
+ # Plan file format
2
+
3
+ The `pirl-plan` skill owns how a Plan file is laid out, named and stored. What each
4
+ section must settle belongs to the
5
+ [Plan contract](../../../.agents/instructions/plan.instructions.md).
6
+
7
+ ## Location and naming
8
+
9
+ - Output directory: the directory the user names for the Plan; when the user
10
+ names none, the Hub root.
11
+ - Package: `<output directory>/<request-slug>_<YYYYMMDD-HHmmss>/`. The
12
+ package directory name is the `plan_set`.
13
+ - Task file: `<NN>-plan-<todo-slug>-<YYYYMMDD-HHmmss>.md`, where `NN` is the
14
+ task number (`T01` → `01`) and the timestamp is the package timestamp, shared
15
+ by every file of the package.
16
+ - Slugs are ASCII kebab-case: lowercase letters, digits and single hyphens.
17
+ - Timestamps use the machine's local time zone and carry its UTC offset, for
18
+ example `+07:00`.
19
+ - Task IDs start at `T01` and are consecutive within the package.
20
+ - Never overwrite an existing file. Review reports of the package are placed
21
+ per the [report format](../../pirl-review/references/review-format.md#location-and-naming).
22
+ - Git ignores package directories anywhere in the Hub, by their name pattern
23
+ ([AGENTS.md](../../../AGENTS.md#git)); a package the user places outside the
24
+ Hub follows the Git rules of that location.
25
+
26
+ ## Frontmatter
27
+
28
+ | Field | Value |
29
+ | --- | --- |
30
+ | `schema` | `pirl-plan/v1` |
31
+ | `content_language` | `en` |
32
+ | `plan_set` | The package directory name |
33
+ | `task_id` | `T01`, `T02`, … |
34
+ | `title` | Task name, quoted |
35
+ | `layer` | `implement`: the layer that carries out the Plan |
36
+ | `status` | `planned` at handoff; Implement later sets `awaiting_confirmation`, `implemented`, `blocked` or `failed`, equal to the Implementation Record's `status` |
37
+ | `depends_on` | Task IDs of the same package; never the task itself, never a cycle |
38
+ | `created_at` | ISO-8601 timestamp with a UTC offset, quoted |
39
+ | `target_repo` | Path of the target repository, relative to the Hub root or absolute; `.` for the Hub itself; an existing directory ([AGENTS.md](../../../AGENTS.md#target-repository)) |
40
+ | `source_request` | Short version of the user's request, quoted |
41
+ | `planning_roles` | Roles registered for `plan` |
42
+ | `implementation_roles` | Roles registered for `implement` |
43
+ | `verification_roles` | Roles registered for `verify`, none of them in `implementation_roles` |
44
+
45
+ Write the frontmatter in the YAML subset the validator reads: one
46
+ `key: value` per line, inline lists such as `[code-analyst, code-reviewer]`,
47
+ quoted strings when a value contains `:` or `#`.
48
+
49
+ ## Body
50
+
51
+ - Exactly four level-two sections, in this order: `What to do`, `How to do it`,
52
+ `Participating roles`, `Required quality gates`. Add only details that change
53
+ how the work is done; do not add per-role reports or checklists as a fifth
54
+ section. Implement later adds one `Implementation Record` section below them.
55
+ - Line forms: `REQ-NN | Source: … | Requirement: …`;
56
+ `ROLE-NN | Type: … | Role: … | Responsible for: … | Input: … | Done when: …`;
57
+ `QG-NN | Requirement: REQ-NN | Required logic: …`.
58
+ - Write in English; keep IDs, data keys, symbols, commands and paths verbatim.
59
+
60
+ ## Placeholders in the template
61
+
62
+ - `{{name}}`: computed values that `scripts/new_plan.py` fills in. When a file
63
+ is written without the script, fill them by the rules above.
64
+ - `<…>`: content the coordinator writes from the settled decisions. A line of
65
+ the form `<If …: …>` stays only when its condition holds.
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ """Create one Plan task file from the plan skill's template, with the computed values filled in.
3
+
4
+ The layout, naming and location rules are owned by ../references/plan-format.md; this script fills only
5
+ what a tool can compute (package name, task ID, file name, timestamp) and leaves every <...> placeholder
6
+ to the coordinator. It never overwrites a file.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import re
14
+ import sys
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+ SKILL = Path(__file__).resolve().parents[1]
19
+ HUB = SKILL.parents[1]
20
+ sys.path.insert(0, str(HUB / ".agents" / "evals"))
21
+ from validate_layer_artifact import LayerArtifactError, read_frontmatter, resolve_path # noqa: E402
22
+
23
+ TEMPLATE = SKILL / "assets" / "plan-template.md"
24
+ SLUG = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
25
+ PLAN_SET = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*_(\d{8}-\d{6})")
26
+ TASK = re.compile(r"T(\d{2,})")
27
+
28
+
29
+ def shown(path: Path, root: Path) -> str:
30
+ """A path as Plans and reports write it: relative to the Hub root when inside it, otherwise absolute."""
31
+
32
+ return path.relative_to(root).as_posix() if path.is_relative_to(root) else path.as_posix()
33
+
34
+
35
+ def create(root: Path, todo_slug: str, request_slug: str | None, plan_set: str | None, out_dir: str | None) -> dict[str, str]:
36
+ if not SLUG.fullmatch(todo_slug):
37
+ raise LayerArtifactError("--todo-slug must be ASCII kebab-case")
38
+ base = resolve_path(out_dir, root) if out_dir else root
39
+ now = datetime.now().astimezone()
40
+ if request_slug is not None:
41
+ if not SLUG.fullmatch(request_slug):
42
+ raise LayerArtifactError("--request-slug must be ASCII kebab-case")
43
+ plan_set = f"{request_slug}_{now:%Y%m%d-%H%M%S}"
44
+ package = base / plan_set
45
+ if package.exists():
46
+ raise LayerArtifactError(f"{shown(package, root)} already exists; pass --plan-set to add a task to it")
47
+ else:
48
+ if not plan_set or not PLAN_SET.fullmatch(plan_set):
49
+ raise LayerArtifactError("--plan-set must have the form <request-slug>_<YYYYMMDD-HHmmss>")
50
+ package = base / plan_set
51
+ if not package.is_dir():
52
+ raise LayerArtifactError(f"{shown(package, root)} is not an existing Plan package")
53
+ stamp = PLAN_SET.fullmatch(plan_set).group(1)
54
+ numbers = [0]
55
+ for item in sorted(package.glob("*.md")) if package.is_dir() else []:
56
+ match = TASK.fullmatch(str(read_frontmatter(item)[0].get("task_id", "")))
57
+ if match:
58
+ numbers.append(int(match.group(1)))
59
+ number = max(numbers) + 1
60
+ task_id = f"T{number:02d}"
61
+ target = package / f"{number:02d}-plan-{todo_slug}-{stamp}.md"
62
+ if target.exists():
63
+ raise LayerArtifactError(f"{shown(target, root)} already exists")
64
+ text = (TEMPLATE.read_text(encoding="utf-8")
65
+ .replace("{{plan_set}}", plan_set)
66
+ .replace("{{task_id}}", task_id)
67
+ .replace("{{created_at}}", now.isoformat(timespec="seconds")))
68
+ package.mkdir(parents=True, exist_ok=True)
69
+ target.write_text(text, encoding="utf-8", newline="\n")
70
+ return {"path": shown(target, root), "plan_set": plan_set, "task_id": task_id}
71
+
72
+
73
+ def main() -> int:
74
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
75
+ parser.add_argument("--todo-slug", required=True, help="ASCII kebab-case slug of this task")
76
+ start = parser.add_mutually_exclusive_group(required=True)
77
+ start.add_argument("--request-slug", help="ASCII kebab-case slug of the request; starts a new package")
78
+ start.add_argument("--plan-set", help="name of an existing package; adds the next task to it")
79
+ parser.add_argument("--out-dir", help="directory holding the package, relative to the Hub root or absolute; default: the Hub root")
80
+ parser.add_argument("--root", type=Path, default=HUB, help="Hub root (default: the Hub containing this script)")
81
+ args = parser.parse_args()
82
+ if hasattr(sys.stdout, "reconfigure"):
83
+ sys.stdout.reconfigure(encoding="utf-8")
84
+ try:
85
+ result = {"passed": True, **create(args.root.resolve(), args.todo_slug, args.request_slug, args.plan_set, args.out_dir)}
86
+ except (LayerArtifactError, OSError) as exc:
87
+ result = {"passed": False, "errors": [str(exc)]}
88
+ print(json.dumps(result, ensure_ascii=False, indent=2))
89
+ return 0 if result["passed"] else 1
90
+
91
+
92
+ if __name__ == "__main__":
93
+ raise SystemExit(main())
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: pirl-review
3
+ description: Runs the Review layer of the PIRL framework. Checks one implementation run of a Plan task against the Plan's quality gates by reasoning on the source, without building or running tests, and writes a report with a Business verdict (findings, risks, blockers) and Evaluations of the Implement roles with the framework improvements applied. Use only when the user's own words ask to review the implementation of a named Plan task or to open a new Review round. Do not use to write or implement a Plan, for a general code review outside a Plan or to edit the framework itself.
4
+ metadata:
5
+ id: .skills/pirl-review
6
+ kind: skill
7
+ layer: review
8
+ version: 1.1.2
9
+ ---
10
+
11
+ # Review
12
+
13
+ Carries out one Review round. The
14
+ [Review contract](../../.agents/instructions/review.instructions.md) owns the
15
+ decisions, the rounds and the exit conditions; the
16
+ [router](../../.agents/instructions/layered-task-execution.instructions.md) owns
17
+ the shared contracts; [AGENTS.md](../../AGENTS.md) owns the core rules, roles
18
+ and target repository. This skill owns the order of the steps, the report
19
+ format and the helper script. Where a step names a section, that section
20
+ decides.
21
+
22
+ ## When to use
23
+
24
+ - The user's own words ask for a Review of an implementation run, per the
25
+ [conditions for opening a layer](../../AGENTS.md#choosing-a-layer).
26
+ - Not for writing a Plan ([`pirl-plan`](../pirl-plan/SKILL.md)), implementing or
27
+ repairing ([`pirl-implement`](../pirl-implement/SKILL.md)), a code review that no Plan
28
+ covers
29
+ ([standalone task](../../.agents/instructions/standalone-and-maintenance.instructions.md#standalone-tasks))
30
+ or [framework maintenance](../../.agents/instructions/standalone-and-maintenance.instructions.md#framework-maintenance).
31
+
32
+ ## Resources
33
+
34
+ | Resource | Use |
35
+ | --- | --- |
36
+ | [`assets/review-template.md`](assets/review-template.md) | Template of one Review report |
37
+ | [`references/review-format.md`](references/review-format.md) | Frontmatter fields, file naming and location, presentation rules |
38
+ | [`scripts/new_review.py`](scripts/new_review.py) | Creates the report of the next round for a Plan task, with the round, `prior_review`, `plan_file` and timestamp filled in |
39
+ | [`validate_layer_artifact.py`](../../.agents/evals/validate_layer_artifact.py) | Checks the report structure before the final response |
40
+ | [`layer_record.py`](../../.agents/evals/layer_record.py) | Pins the framework version, finds the reviewed Implement record and writes the run's record |
41
+
42
+ ## Procedure
43
+
44
+ Run the commands from the Hub root.
45
+
46
+ 1. **Open the run.** Apply the [core rules](../../AGENTS.md#core-rules) and
47
+ [CONTEXT.md](../../CONTEXT.md). Run
48
+ `python -B .agents/evals/layer_record.py start` and keep the returned
49
+ `framework_ref`
50
+ ([Timing and evaluator](../../.agents/instructions/layered-task-execution.instructions.md#timing-and-evaluator)).
51
+ 2. **Read the inputs**
52
+ ([Inputs and permissions](../../.agents/instructions/review.instructions.md#inputs-and-permissions)):
53
+ the Plan, its Implementation Record, the diff and source of the Plan's
54
+ `target_repo`, and earlier reports for the same task. Identify the snapshot
55
+ before concluding.
56
+ 3. **Round.** Decide whether this work is a new round or belongs to the current
57
+ one ([Report and rounds](../../.agents/instructions/review.instructions.md#report-and-rounds)).
58
+ A new round creates its report in the directory the user named, or by
59
+ default under the Hub root
60
+ ([format](references/review-format.md#location-and-naming)); the current
61
+ round edits its existing report:
62
+
63
+ ```powershell
64
+ python -B .skills/pirl-review/scripts/new_review.py --plan <Plan file> --todo-slug <todo-slug> [--out-dir <dir>]
65
+ ```
66
+
67
+ 4. **Team.** Pick `review_roles` by the "Select when" column
68
+ ([AI Agent roles at the Review layer](../../.agents/instructions/review.instructions.md#ai-agent-roles-at-the-review-layer));
69
+ delegate each role to a separate agent per the
70
+ [assignment principles](../../.agents/instructions/layered-task-execution.instructions.md#assignment-principles).
71
+ 5. **Business**
72
+ ([contract](../../.agents/instructions/review.instructions.md#business-prove-or-refute-each-suspicion)):
73
+ conclude each QG, keep only grounded FINDs, RISKs and BLOCKs, and write the
74
+ `Business` section.
75
+ 6. **Evaluations and self-evaluation.** `process-auditor`, in a separate agent,
76
+ evaluates the roles of the reviewed Implement run
77
+ ([Evaluations](../../.agents/instructions/review.instructions.md#evaluations-assessing-implement-roles))
78
+ and the Review run itself
79
+ ([Self-evaluation and improvement](../../.agents/instructions/layered-task-execution.instructions.md#self-evaluation-and-improvement)),
80
+ applies improvements and writes the `Evaluations` section. The reviewed
81
+ record is found with:
82
+
83
+ ```powershell
84
+ python -B .agents/evals/layer_record.py query --layer implement --plan-set <plan-set> --task <task-id>
85
+ ```
86
+
87
+ 7. **Validate** the report and fix until it passes:
88
+
89
+ ```powershell
90
+ python -B .agents/evals/validate_layer_artifact.py --kind review --path <report file>
91
+ ```
92
+
93
+ 8. **Write the record** of the round, with `evaluates` and `task.report_file`
94
+ set:
95
+
96
+ ```powershell
97
+ python -B .agents/evals/layer_record.py append --input <JSON file>
98
+ python -B .agents/evals/layer_record.py check
99
+ ```
100
+
101
+ 9. **Clean up** temporary resources
102
+ ([router](../../.agents/instructions/layered-task-execution.instructions.md#cleaning-up-temporary-resources-before-handoff)).
103
+ 10. **Report**
104
+ ([Finishing and next permissions](../../.agents/instructions/review.instructions.md#finishing-and-next-permissions),
105
+ [Reporting](../../.agents/instructions/layered-task-execution.instructions.md#reporting)).
106
+ Stop there: a repair run or a new round opens only when the user asks.
@@ -0,0 +1,92 @@
1
+ ---
2
+ schema: pirl-review/v1
3
+ content_language: en
4
+ plan_file: "{{plan_file}}"
5
+ task_id: {{task_id}}
6
+ review_round: {{review_round}}
7
+ prior_review: {{prior_review}}
8
+ review_roles: [code-analyst, code-reviewer, business-analyst, solution-architect]
9
+ repair_roles: []
10
+ business_verdict: PASS
11
+ verdict: PASS
12
+ status: completed
13
+ reviewed_at: "{{reviewed_at}}"
14
+ implementation_target: "<diff/commit/paths reviewed>"
15
+ ---
16
+
17
+ # Review {{review_round_padded}}: {{task_id}} — {{title}}
18
+
19
+ ## Business
20
+
21
+ ### Quality gates
22
+
23
+ | QG | Result | Evidence |
24
+ | --- | --- | --- |
25
+ | QG-01 | <PASS / FAIL / BLOCKED> | <source/symbol, logic and the result of the check> |
26
+
27
+ ### Findings
28
+
29
+ #### FIND-01
30
+
31
+ | Field | Content |
32
+ | --- | --- |
33
+ | QG | QG-01 |
34
+ | Impact | <realistic trigger and consequence> |
35
+ | Source | <file/symbol or trusted contract> |
36
+ | Cause | <the faulty mechanism> |
37
+ | Challenge | <grounds that refute competing explanations> |
38
+ | Roles | <role, ...> |
39
+ | Target | `<file to handle>` |
40
+ | Instructions | <concrete work for each role, in order> |
41
+ | Disposition | <repair / replan> |
42
+
43
+ ### Risks
44
+
45
+ #### RISK-01
46
+
47
+ | Field | Content |
48
+ | --- | --- |
49
+ | QG | QG-02 |
50
+ | Impact | <realistic exposure and limits> |
51
+ | Source | <file/symbol> |
52
+ | Cause | <the mechanism that creates the risk> |
53
+ | Challenge | <why the risk stands while the QG still holds> |
54
+ | Roles | <role, ...> |
55
+ | Target | `<related file>` |
56
+ | Instructions | <how to mitigate when assigned> |
57
+ | Revisit when | <concrete condition> |
58
+
59
+ ### Blockers
60
+
61
+ #### BLOCK-01
62
+
63
+ | Field | Content |
64
+ | --- | --- |
65
+ | QG | QG-03 |
66
+ | Missing | <mandatory input not yet available> |
67
+ | Source | <evidence of what is missing> |
68
+ | Cause | <the mechanism that blocks the conclusion> |
69
+ | Owner | <role or provider of the input> |
70
+ | Target | `<related file>` |
71
+ | Instructions | <action to unblock> |
72
+ | Disposition | <retry / escalate> |
73
+ | Condition | <change needed before continuing> |
74
+
75
+ ### Next
76
+
77
+ | Field | Content |
78
+ | --- | --- |
79
+ | Next | <owner/layer, input file and condition to continue> |
80
+
81
+ ## Evaluations
82
+
83
+ ### EVAL-01
84
+
85
+ | Field | Content |
86
+ | --- | --- |
87
+ | Role | <Implement implementation or verification role> |
88
+ | Result | <PASS / FAIL / BLOCKED> |
89
+ | Issue | <the role's grounded wrong decision; or none> |
90
+ | Cause | <instruction / ineffective_fix / input / execution with `issue_key`; or none> |
91
+ | Improvement | <file and section edited, old → new version; or pending the user (safety); or not needed> |
92
+ | Evidence | <Implementation Record, Implement record, sources/diff checked> |
@@ -0,0 +1,70 @@
1
+ # Review report format
2
+
3
+ The `pirl-review` skill owns how a Review report is laid out, named, stored and
4
+ presented. What each conclusion must prove, and when a new round opens, belongs
5
+ to the [Review contract](../../../.agents/instructions/review.instructions.md).
6
+
7
+ ## Location and naming
8
+
9
+ - Output directory: the directory the user names for the report; when the user
10
+ names none, the Hub root.
11
+ - Report:
12
+ `<output directory>/<plan-set>/<task-id-lower>-<todo-slug>/round-<NN>-review-<todo-slug>-<YYYYMMDD-HHmmss>.md`,
13
+ where `NN` is the two-digit round number. When the Plan package also sits
14
+ under the Hub root, the report lands inside the package directory, next to
15
+ the Plan it reviews. Inside the Hub, the `<plan-set>/` directory is ignored by
16
+ Git like the Plan package ([AGENTS.md](../../../AGENTS.md#git)).
17
+ - Slugs are ASCII kebab-case; timestamps use the machine's local
18
+ time zone and carry its UTC offset.
19
+ - Every round has its own file; never overwrite or delete another round.
20
+
21
+ ## Frontmatter
22
+
23
+ | Field | Value |
24
+ | --- | --- |
25
+ | `schema` | `pirl-review/v1` |
26
+ | `content_language` | `en` |
27
+ | `plan_file` | The reviewed Plan file: path relative to the Hub root, or absolute when it lives outside the Hub; quoted |
28
+ | `task_id` | The Plan's `task_id` |
29
+ | `review_round` | `1`, `2`, … |
30
+ | `prior_review` | `null` in round 1; afterwards the report of the round just before, as a path like `plan_file` |
31
+ | `review_roles` | Roles registered for `review`, in the order of the contract's role table |
32
+ | `repair_roles` | Roles registered for `implement`; `[]` while a `replan` remains or nothing needs repair |
33
+ | `business_verdict` | `PASS`, `FAIL` or `BLOCKED` |
34
+ | `verdict` | Equal to `business_verdict` |
35
+ | `status` | `completed` or `blocked` |
36
+ | `reviewed_at` | ISO-8601 timestamp with a UTC offset, quoted |
37
+ | `implementation_target` | The diff, commit or paths reviewed, including a shared snapshot |
38
+
39
+ ## Presentation rules
40
+
41
+ Keep the two level-two sections `Business` and `Evaluations`; use level-three/
42
+ four headings to divide the content inside. Present QGs as a three-column
43
+ table; each FIND/RISK/BLOCK and EVAL has its own two-column `Field | Content`
44
+ table under a heading containing its ID. The `Next` part uses a two-column
45
+ table. Do not merge several fields into one long bullet or cram all fields of a
46
+ finding/evaluation into one overly wide table.
47
+
48
+ This is only presentation: keep all fields, IDs, conclusions, evidence and
49
+ instructions; do not drop information to make a table shorter. In each cell,
50
+ use `<br>` when you need to separate several sources or processing steps; keep
51
+ paths, symbols and commands intact. Escape a `|` that belongs to cell content
52
+ as `\|`, including inside inline code, so it does not create a column. This
53
+ escape rule applies only to Markdown tables and does not change values in the
54
+ frontmatter. Put a blank line before/after each table.
55
+
56
+ Replace placeholders with actual content and choose one value for each status
57
+ or disposition. Each QG has exactly one row in the Quality gates table. Record
58
+ only FINDs/RISKs/BLOCKs that actually exist; a group with no items uses one
59
+ short sentence under its heading, with no empty table or fake ID. Number each
60
+ kind consecutively, keeping the order findings, risks, then blockers. EVALs
61
+ cover the settled Implement team in the Implementation Record's order; do not
62
+ add an inventory or a third level-two section that repeats the handoff.
63
+
64
+ ## Placeholders in the template
65
+
66
+ - `{{name}}`: computed values that `scripts/new_review.py` fills in from the
67
+ Plan and the earlier rounds. When a report is written without the script,
68
+ fill them by the rules above.
69
+ - `<…>` and the `a / b` alternatives: the coordinator writes the actual content
70
+ and keeps exactly one alternative.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env python3
2
+ """Create the report of the next Review round for a Plan task from the review skill's template.
3
+
4
+ The layout, naming and location rules are owned by ../references/review-format.md; this script fills only
5
+ what a tool can compute (plan_file, task ID, round, prior_review, timestamp, title) and leaves every <...>
6
+ placeholder to the coordinator. Work that belongs to the current round edits that round's report instead.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import re
14
+ import sys
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+ SKILL = Path(__file__).resolve().parents[1]
19
+ HUB = SKILL.parents[1]
20
+ sys.path.insert(0, str(HUB / ".agents" / "evals"))
21
+ from validate_layer_artifact import PLAN_SCHEMA, LayerArtifactError, read_frontmatter, resolve_path # noqa: E402
22
+
23
+ TEMPLATE = SKILL / "assets" / "review-template.md"
24
+ SLUG = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
25
+ ROUND = re.compile(r"round-(\d{2,})-review-.+\.md")
26
+
27
+
28
+ def shown(path: Path, root: Path) -> str:
29
+ """A path as Plans and reports write it: relative to the Hub root when inside it, otherwise absolute."""
30
+
31
+ return path.relative_to(root).as_posix() if path.is_relative_to(root) else path.as_posix()
32
+
33
+
34
+ def create(root: Path, plan: str, todo_slug: str, out_dir: str | None) -> dict[str, object]:
35
+ if not SLUG.fullmatch(todo_slug):
36
+ raise LayerArtifactError("--todo-slug must be ASCII kebab-case")
37
+ plan_path = resolve_path(plan, root)
38
+ if not plan_path.is_file():
39
+ raise LayerArtifactError(f"{plan} is not an existing Plan file (relative to the Hub root or absolute)")
40
+ metadata, _ = read_frontmatter(plan_path)
41
+ if metadata.get("schema") != PLAN_SCHEMA or not metadata.get("plan_set") or not metadata.get("task_id"):
42
+ raise LayerArtifactError(f"{plan} is not a {PLAN_SCHEMA} Plan with plan_set and task_id")
43
+ task_id = str(metadata["task_id"])
44
+ folder = (resolve_path(out_dir, root) if out_dir else root) / str(metadata["plan_set"]) / f"{task_id.lower()}-{todo_slug}"
45
+ rounds = sorted((int(m.group(1)), p) for p in (folder.glob("*.md") if folder.is_dir() else []) if (m := ROUND.fullmatch(p.name)))
46
+ number = rounds[-1][0] + 1 if rounds else 1
47
+ prior = json.dumps(shown(rounds[-1][1], root), ensure_ascii=False) if rounds else "null"
48
+ now = datetime.now().astimezone()
49
+ target = folder / f"round-{number:02d}-review-{todo_slug}-{now:%Y%m%d-%H%M%S}.md"
50
+ if target.exists():
51
+ raise LayerArtifactError(f"{shown(target, root)} already exists")
52
+ text = (TEMPLATE.read_text(encoding="utf-8")
53
+ .replace("{{plan_file}}", shown(plan_path, root))
54
+ .replace("{{task_id}}", task_id)
55
+ .replace("{{review_round_padded}}", f"{number:02d}")
56
+ .replace("{{review_round}}", str(number))
57
+ .replace("{{prior_review}}", prior)
58
+ .replace("{{reviewed_at}}", now.isoformat(timespec="seconds"))
59
+ .replace("{{title}}", str(metadata.get("title") or "<Task name>")))
60
+ folder.mkdir(parents=True, exist_ok=True)
61
+ target.write_text(text, encoding="utf-8", newline="\n")
62
+ return {"path": shown(target, root), "review_round": number, "prior_review": json.loads(prior)}
63
+
64
+
65
+ def main() -> int:
66
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
67
+ parser.add_argument("--plan", required=True, help="the reviewed Plan file, relative to the Hub root or absolute")
68
+ parser.add_argument("--todo-slug", required=True, help="ASCII kebab-case slug of the reviewed task")
69
+ parser.add_argument("--out-dir", help="directory holding the report's package folder, relative to the Hub root or absolute; default: the Hub root")
70
+ parser.add_argument("--root", type=Path, default=HUB, help="Hub root (default: the Hub containing this script)")
71
+ args = parser.parse_args()
72
+ if hasattr(sys.stdout, "reconfigure"):
73
+ sys.stdout.reconfigure(encoding="utf-8")
74
+ try:
75
+ result = {"passed": True, **create(args.root.resolve(), args.plan, args.todo_slug, args.out_dir)}
76
+ except (LayerArtifactError, OSError) as exc:
77
+ result = {"passed": False, "errors": [str(exc)]}
78
+ print(json.dumps(result, ensure_ascii=False, indent=2))
79
+ return 0 if result["passed"] else 1
80
+
81
+
82
+ if __name__ == "__main__":
83
+ raise SystemExit(main())