@tyroneross/build-loop 0.30.3 → 0.34.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 (170) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +4 -4
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/AGENTS.md +9 -0
  6. package/README.md +8 -7
  7. package/agents/advisor.md +111 -0
  8. package/agents/alignment-checker.md +11 -0
  9. package/agents/assessment-orchestrator.md +1 -1
  10. package/agents/build-orchestrator.md +28 -33
  11. package/agents/fact-checker.md +1 -1
  12. package/agents/fix-critique.md +1 -1
  13. package/agents/independent-auditor.md +1 -1
  14. package/agents/overfitting-reviewer.md +1 -1
  15. package/agents/plan-critic.md +5 -4
  16. package/agents/promotion-reviewer.md +1 -1
  17. package/agents/retrospective-synthesizer.md +20 -3
  18. package/agents/scope-auditor.md +1 -1
  19. package/agents/security-reviewer.md +1 -1
  20. package/agents/self-improvement-architect.md +1 -1
  21. package/commands/{handoff.md → compose-handoff.md} +4 -4
  22. package/commands/{optimize.md → optimize-run.md} +1 -1
  23. package/commands/{agent-rally-point.md → rally-point.md} +3 -3
  24. package/commands/{research.md → research-run.md} +1 -1
  25. package/commands/setup-memory.md +32 -0
  26. package/commands/{plan-verify.md → verify-plan.md} +1 -1
  27. package/docs/agent-surface-policy.md +38 -20
  28. package/docs/memory-setup.md +19 -14
  29. package/hooks/_resolve_python.sh +28 -0
  30. package/hooks/closeout.sh +58 -0
  31. package/hooks/git/pre-push +69 -6
  32. package/hooks/hooks.json +34 -13
  33. package/hooks/post-push-closeout.sh +73 -0
  34. package/hooks/session-start-closeout.sh +69 -0
  35. package/hooks/session-start-memory.sh +27 -0
  36. package/hooks/session-start-plugin-heal.sh +33 -0
  37. package/hooks/test_closeout.sh +124 -0
  38. package/package.json +7 -4
  39. package/plugin-artifacts/codex/.codex-plugin/plugin.json +41 -0
  40. package/plugin-artifacts/codex/AGENTS.md +499 -0
  41. package/plugin-artifacts/codex/BUILD-ARTIFACT.md +5 -0
  42. package/plugin-artifacts/codex/LICENSE +202 -0
  43. package/plugin-artifacts/codex/README.md +404 -0
  44. package/plugin-artifacts/codex/docs/agent-surface-policy.md +63 -0
  45. package/plugin-artifacts/codex/references/advisor-dispatch-ladder.md +62 -0
  46. package/plugin-artifacts/codex/references/agent-role-taxonomy.md +101 -0
  47. package/plugin-artifacts/codex/references/autonomous-and-per-commit-modes.md +161 -0
  48. package/plugin-artifacts/codex/references/autonomy-config.md +231 -0
  49. package/plugin-artifacts/codex/references/capability-routing.md +182 -0
  50. package/plugin-artifacts/codex/references/codex-subagents.md +106 -0
  51. package/plugin-artifacts/codex/references/coordination-file-template.md +181 -0
  52. package/plugin-artifacts/codex/references/coordination-rules.md +283 -0
  53. package/plugin-artifacts/codex/references/dogfood-reload-checkpoint.md +112 -0
  54. package/plugin-artifacts/codex/references/halt-and-ask-protocol.md +102 -0
  55. package/plugin-artifacts/codex/references/implementer-envelope-schema.md +302 -0
  56. package/plugin-artifacts/codex/references/intent-capability-pack.md +257 -0
  57. package/plugin-artifacts/codex/references/intent-exploration-prompts.md +96 -0
  58. package/plugin-artifacts/codex/references/leadership.md +72 -0
  59. package/plugin-artifacts/codex/references/memory-systems.md +238 -0
  60. package/plugin-artifacts/codex/references/memory.md +297 -0
  61. package/plugin-artifacts/codex/references/model-tier-mapping.md +257 -0
  62. package/plugin-artifacts/codex/references/modular-systems-pack.md +96 -0
  63. package/plugin-artifacts/codex/references/phase-1-assess.md +231 -0
  64. package/plugin-artifacts/codex/references/phase-2-plan.md +77 -0
  65. package/plugin-artifacts/codex/references/phase-3-execute.md +47 -0
  66. package/plugin-artifacts/codex/references/phase-4-review.md +320 -0
  67. package/plugin-artifacts/codex/references/phase-5-iterate.md +71 -0
  68. package/plugin-artifacts/codex/references/phase-6-learn.md +56 -0
  69. package/plugin-artifacts/codex/references/recent-design-structures.md +274 -0
  70. package/plugin-artifacts/codex/references/research-trigger-policy.md +140 -0
  71. package/plugin-artifacts/codex/references/runtime-smoke-triggers.md +42 -0
  72. package/plugin-artifacts/codex/references/self-review.md +234 -0
  73. package/plugin-artifacts/codex/references/single-writer-commit-protocol.md +90 -0
  74. package/plugin-artifacts/codex/references/task-capture-policy.md +68 -0
  75. package/plugin-artifacts/codex/references/ui-io-contract.md +116 -0
  76. package/plugin-artifacts/codex/references/ui-spotcheck-protocol.md +65 -0
  77. package/plugin-artifacts/codex/skills/build-loop/SKILL.md +372 -0
  78. package/plugin-artifacts/codex/skills/build-loop/detect-plugins.mjs +82 -0
  79. package/plugin-artifacts/codex/skills/build-loop/eval-guide.md +65 -0
  80. package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +549 -0
  81. package/plugin-artifacts/codex/skills/build-loop/phases/fact-check.md +42 -0
  82. package/plugin-artifacts/codex/skills/build-loop/phases/ui-validation.md +267 -0
  83. package/plugin-artifacts/codex/skills/build-loop/references/advisor-dispatch-ladder.md +62 -0
  84. package/plugin-artifacts/codex/skills/build-loop/references/apple-native-planning.md +439 -0
  85. package/plugin-artifacts/codex/skills/build-loop/references/autonomous-and-per-commit-modes.md +161 -0
  86. package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +182 -0
  87. package/plugin-artifacts/codex/skills/build-loop/references/codex-subagents.md +106 -0
  88. package/plugin-artifacts/codex/skills/build-loop/references/coordination.md +161 -0
  89. package/plugin-artifacts/codex/skills/build-loop/references/correction-aware-capture.md +177 -0
  90. package/plugin-artifacts/codex/skills/build-loop/references/independent-auditor.md +72 -0
  91. package/plugin-artifacts/codex/skills/build-loop/references/intent-capability-pack.md +257 -0
  92. package/plugin-artifacts/codex/skills/build-loop/references/intent-exploration-prompts.md +96 -0
  93. package/plugin-artifacts/codex/skills/build-loop/references/leadership.md +72 -0
  94. package/plugin-artifacts/codex/skills/build-loop/references/memory.md +297 -0
  95. package/plugin-artifacts/codex/skills/build-loop/references/modular-systems-pack.md +96 -0
  96. package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +129 -0
  97. package/plugin-artifacts/codex/skills/build-loop/references/pay-it-forward-arch.md +98 -0
  98. package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +231 -0
  99. package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +77 -0
  100. package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +47 -0
  101. package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +320 -0
  102. package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +71 -0
  103. package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +56 -0
  104. package/plugin-artifacts/codex/skills/build-loop/references/recent-design-structures.md +274 -0
  105. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
  106. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
  107. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
  108. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
  109. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
  110. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
  111. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
  112. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
  113. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
  114. package/plugin-artifacts/codex/skills/build-loop/references/self-recursive-dev.md +77 -0
  115. package/plugin-artifacts/codex/skills/build-loop/references/self-review.md +234 -0
  116. package/plugin-artifacts/codex/skills/build-loop/references/ui-io-contract.md +116 -0
  117. package/plugin-artifacts/codex/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
  118. package/plugin-artifacts/codex/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
  119. package/plugin-artifacts/codex/skills/build-loop/templates/backlog-item.md +35 -0
  120. package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +96 -0
  121. package/plugin-artifacts/codex/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
  122. package/plugin-artifacts/codex/skills/build-loop/templates/ux-fix-plan.md +40 -0
  123. package/scripts/_paths.py +59 -13
  124. package/scripts/build_codex_plugin_artifact.py +314 -0
  125. package/scripts/check_cache_sync.py +1 -1
  126. package/scripts/install_memory.py +11 -3
  127. package/scripts/prune_plugin_cache.py +225 -11
  128. package/scripts/sync_navgator_lessons.py +25 -0
  129. package/scripts/sync_plugin_cache.py +66 -36
  130. package/skills/agent-rally-point/SKILL.md +1 -1
  131. package/skills/agent-rally-watcher/SKILL.md +1 -1
  132. package/skills/build-loop/SKILL.md +11 -155
  133. package/skills/build-loop/references/advisor-dispatch-ladder.md +62 -0
  134. package/skills/build-loop/references/autonomous-and-per-commit-modes.md +161 -0
  135. package/skills/build-loop/references/capability-routing.md +4 -3
  136. package/skills/build-loop/references/codex-subagents.md +8 -0
  137. package/skills/build-loop/references/coordination.md +1 -1
  138. package/skills/build-loop/references/intent-capability-pack.md +88 -1
  139. package/skills/build-loop/references/memory.md +17 -2
  140. package/skills/build-loop/references/phase-1-assess.md +13 -0
  141. package/skills/build-loop/references/phase-2-plan.md +1 -1
  142. package/skills/build-loop/references/phase-4-review.md +12 -0
  143. package/skills/build-loop/references/phase-6-learn.md +1 -1
  144. package/skills/build-loop/references/self-review.md +4 -2
  145. package/skills/build-loop/templates/codex-worker-prompt.md +41 -34
  146. package/skills/handoff/SKILL.md +3 -3
  147. package/skills/loop-builder/SKILL.md +98 -0
  148. package/skills/loop-builder/presets/active-project-evidence.yaml +97 -0
  149. package/skills/loop-builder/presets/generic-artifact-loop.yaml +95 -0
  150. package/skills/loop-builder/presets/presentation-audit.yaml +96 -0
  151. package/skills/loop-builder/presets/research-synthesis.yaml +96 -0
  152. package/skills/loop-builder/presets/source-ingestion-raw-data-audit.yaml +96 -0
  153. package/skills/loop-builder/references/spec-format.md +80 -0
  154. package/skills/loop-builder/scripts/loop_builder.py +346 -0
  155. package/skills/model-tiering/SKILL.md +65 -40
  156. package/skills/optimize/SKILL.md +2 -2
  157. package/skills/plan-verify/SKILL.md +1 -1
  158. package/skills/plugin-builder/references/distribution.md +8 -0
  159. package/skills/research/SKILL.md +33 -6
  160. package/skills/runtime-parity-verification/SKILL.md +51 -0
  161. package/skills/spec-writing/SKILL.md +19 -0
  162. package/templates/memory/README.md +65 -0
  163. package/templates/memory/charter.md.template +46 -0
  164. package/templates/memory/manifest.json +24 -4
  165. package/codex-skills/debug-loop/SKILL.md +0 -18
  166. package/codex-skills/handoff/SKILL.md +0 -22
  167. package/codex-skills/knowledge/SKILL.md +0 -21
  168. package/codex-skills/optimize/SKILL.md +0 -18
  169. package/codex-skills/research/SKILL.md +0 -18
  170. /package/commands/{knowledge-review.md → review-knowledge.md} +0 -0
@@ -0,0 +1,96 @@
1
+ {
2
+ "id": "research-synthesis",
3
+ "title": "Research Synthesis Loop",
4
+ "summary": "Answer a question with explicit source strategy, evidence extraction, confidence, and durable citations.",
5
+ "inputs": [
6
+ "research question",
7
+ "decision use",
8
+ "freshness requirement",
9
+ "candidate sources",
10
+ "source quality rubric"
11
+ ],
12
+ "outputs": [
13
+ "source strategy",
14
+ "evidence table",
15
+ "synthesis memo",
16
+ "claim verification log",
17
+ "reusable search strategy"
18
+ ],
19
+ "phases": {
20
+ "assess": {
21
+ "goal": "Frame the question, scope, decision use, freshness need, and inclusion/exclusion rules.",
22
+ "required": ["question", "decision_use", "freshness_requirement", "inclusion_exclusion"]
23
+ },
24
+ "plan": {
25
+ "goal": "Plan source collection, extraction fields, and confidence markers.",
26
+ "required": ["source_strategy", "extraction_schema", "confidence_scheme"]
27
+ },
28
+ "produce": {
29
+ "goal": "Collect sources, extract evidence, and synthesize the answer.",
30
+ "required": ["source_list", "evidence_table", "synthesis"]
31
+ },
32
+ "review": {
33
+ "goal": "Verify claims, dates, statistics, and contested points.",
34
+ "required": ["claim_inventory", "verification_log", "weak_claims"]
35
+ },
36
+ "learn": {
37
+ "goal": "Persist the report, source quirks, and reusable search strategy.",
38
+ "required": ["saved_report", "search_strategy", "source_quirks"]
39
+ }
40
+ },
41
+ "validators": [
42
+ {
43
+ "id": "claim_citation",
44
+ "pass_condition": "Every material claim has a citation or is marked inferred/unsupported.",
45
+ "method": "review"
46
+ },
47
+ {
48
+ "id": "freshness_trace",
49
+ "pass_condition": "Time-sensitive claims include source publication or capture date.",
50
+ "method": "review"
51
+ },
52
+ {
53
+ "id": "source_quality",
54
+ "pass_condition": "Each source has a quality tier and role in the answer.",
55
+ "method": "review"
56
+ },
57
+ {
58
+ "id": "weak_claim_handling",
59
+ "pass_condition": "Unsupported claims are removed, downgraded, or labeled explicitly.",
60
+ "method": "review"
61
+ }
62
+ ],
63
+ "gates": {
64
+ "confirm": ["external_send", "legal_or_compliance_assertion", "sensitive_data_exposure"],
65
+ "surface": ["people_impacting_decision", "money_movement"]
66
+ },
67
+ "skill_chain": {
68
+ "intake": {
69
+ "primary": ["research"],
70
+ "optional": [],
71
+ "fallback": "Use local repo/wiki scan and explicit source list.",
72
+ "handoff_artifact": "research_packet.md"
73
+ },
74
+ "produce": {
75
+ "primary": ["research"],
76
+ "optional": ["pyramid-principle:pyramid-short-form"],
77
+ "fallback": "Draft synthesis from evidence table with confidence markers.",
78
+ "handoff_artifact": "synthesis_memo.md"
79
+ },
80
+ "review": {
81
+ "primary": ["research"],
82
+ "optional": [],
83
+ "fallback": "Run claim inventory and citation checks manually.",
84
+ "handoff_artifact": "claim_verification_log.md"
85
+ },
86
+ "learn": {
87
+ "primary": ["build-loop:knowledge"],
88
+ "optional": [],
89
+ "fallback": "Persist source quirks and search strategy in the report.",
90
+ "handoff_artifact": "research_lessons.md"
91
+ }
92
+ },
93
+ "learn": {
94
+ "store": ["search_strategy", "source_quirks", "claim_failures", "useful_sources"]
95
+ }
96
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "id": "source-ingestion-raw-data-audit",
3
+ "title": "Source Ingestion and Raw Data Audit Loop",
4
+ "summary": "Inventory, dedupe, preserve, and audit source artifacts before promoting claims or source cards.",
5
+ "inputs": [
6
+ "source folder",
7
+ "candidate files",
8
+ "spreadsheets or survey exports",
9
+ "artifact versions",
10
+ "project question"
11
+ ],
12
+ "outputs": [
13
+ "source inventory",
14
+ "duplicate and version map",
15
+ "raw-data manifest",
16
+ "ingestion priority list",
17
+ "source-card drafting queue"
18
+ ],
19
+ "phases": {
20
+ "assess": {
21
+ "goal": "Identify source roots, sensitive material, project questions, and raw-data-bearing files.",
22
+ "required": ["source_roots", "sensitivity_labels", "project_questions", "raw_data_candidates"]
23
+ },
24
+ "plan": {
25
+ "goal": "Prioritize ingestion by decision relevance, source quality, and dedupe risk.",
26
+ "required": ["ingestion_order", "duplicate_strategy", "raw_preservation_strategy"]
27
+ },
28
+ "produce": {
29
+ "goal": "Write inventory, duplicate map, raw-data manifest, and source-card queue.",
30
+ "required": ["hashes", "row_column_counts", "sheet_names", "filters_or_denominators"]
31
+ },
32
+ "review": {
33
+ "goal": "Verify version choices and that quantitative claims can trace to raw or surrogate data.",
34
+ "required": ["version_verdicts", "raw_trace_checks", "uncertain_items"]
35
+ },
36
+ "learn": {
37
+ "goal": "Capture source quirks and future ingestion rules.",
38
+ "required": ["source_quirks", "reusable_ingestion_rules"]
39
+ }
40
+ },
41
+ "validators": [
42
+ {
43
+ "id": "source_inventory_complete",
44
+ "pass_condition": "Every candidate source has path, type, size or count proxy, and ingestion status.",
45
+ "method": "code-or-review"
46
+ },
47
+ {
48
+ "id": "duplicate_resolution",
49
+ "pass_condition": "Same-title or same-hash files are grouped with an explicit keep/compare/defer verdict.",
50
+ "method": "review"
51
+ },
52
+ {
53
+ "id": "raw_data_trace",
54
+ "pass_condition": "Every quantitative source links to an original file or stable surrogate with sheet/table names and row/column counts.",
55
+ "method": "code-or-review"
56
+ },
57
+ {
58
+ "id": "uncertain_items_flagged",
59
+ "pass_condition": "Uncertain, inaccessible, or sensitive items are listed instead of silently skipped.",
60
+ "method": "review"
61
+ }
62
+ ],
63
+ "gates": {
64
+ "confirm": ["sensitive_data_exposure", "irreversible_source_change"],
65
+ "surface": ["legal_or_compliance_assertion"]
66
+ },
67
+ "skill_chain": {
68
+ "intake": {
69
+ "primary": ["research"],
70
+ "optional": ["doc"],
71
+ "fallback": "Run filesystem inventory and summarize file types manually.",
72
+ "handoff_artifact": "source_inventory.md"
73
+ },
74
+ "produce": {
75
+ "primary": [],
76
+ "optional": ["research"],
77
+ "fallback": "Use deterministic scripts or shell inventory for hashes and counts.",
78
+ "handoff_artifact": "raw_data_manifest.md"
79
+ },
80
+ "review": {
81
+ "primary": ["research"],
82
+ "optional": [],
83
+ "fallback": "Review duplicate/version choices against file metadata and source content.",
84
+ "handoff_artifact": "ingestion_review.md"
85
+ },
86
+ "learn": {
87
+ "primary": ["build-loop:knowledge"],
88
+ "optional": [],
89
+ "fallback": "Append durable ingestion lessons to the report.",
90
+ "handoff_artifact": "ingestion_lessons.md"
91
+ }
92
+ },
93
+ "learn": {
94
+ "store": ["source_quirks", "dedupe_rules", "raw_data_rules", "uncertain_item_patterns"]
95
+ }
96
+ }
@@ -0,0 +1,80 @@
1
+ # Loop Spec Format
2
+
3
+ <!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
4
+
5
+ A focused-loop spec is the domain contract for a reusable workflow. The runner owns phase movement, validation order, iteration, gates, queues, and learn/memory. The spec owns the artifact vocabulary.
6
+
7
+ ## Required Fields
8
+
9
+ | Field | Purpose |
10
+ |---|---|
11
+ | `schema_version` | Loop spec version. Start at `1`. |
12
+ | `id` | Kebab-case loop identifier. |
13
+ | `title` | Human-readable loop name. |
14
+ | `summary` | One-sentence purpose. |
15
+ | `inputs` | Accepted source materials or state surfaces. |
16
+ | `outputs` | Artifacts the loop produces. |
17
+ | `phases` | Domain-specific work contract per phase. |
18
+ | `validators` | Pass/fail checks used during review. |
19
+ | `gates` | Human confirmation boundaries. |
20
+ | `skill_chain` | Optional phase-to-skill routing plan. |
21
+ | `learn` | Durable reuse payload. |
22
+
23
+ ## Skill Chain Shape
24
+
25
+ `skill_chain` is a plan for chaining specialized skills. It is advisory unless the runner or host can prove the named skill exists.
26
+
27
+ Each phase may declare:
28
+
29
+ | Field | Purpose |
30
+ |---|---|
31
+ | `primary` | Preferred skills for this phase. |
32
+ | `optional` | Skills to use when source type or scope warrants them. |
33
+ | `fallback` | What to do if the skill is unavailable. |
34
+ | `handoff_artifact` | Required output from the chained skill. |
35
+
36
+ Example:
37
+
38
+ ```yaml
39
+ skill_chain:
40
+ intake:
41
+ primary:
42
+ - research
43
+ optional:
44
+ - doc
45
+ fallback: "Manual source inventory with cited file paths."
46
+ handoff_artifact: "source_inventory.md"
47
+ ```
48
+
49
+ ## Gate Rules
50
+
51
+ Keep common gates centralized. Loop specs should name examples, not redefine policy.
52
+
53
+ Standard confirmation gates:
54
+
55
+ - external_send
56
+ - sensitive_data_exposure
57
+ - money_movement
58
+ - legal_or_compliance_assertion
59
+ - production_or_customer_operation
60
+ - people_impacting_decision
61
+ - irreversible_source_change
62
+
63
+ ## Validator Rules
64
+
65
+ Validators should be binary. Avoid vague scores. If the check requires judgment, phrase it as a pass/fail rubric with named evidence.
66
+
67
+ Good:
68
+
69
+ ```yaml
70
+ - id: source_trace
71
+ pass_condition: "Every material claim cites a source path, slide, row, transcript timestamp, or explicitly marked assumption."
72
+ method: "review"
73
+ ```
74
+
75
+ Weak:
76
+
77
+ ```yaml
78
+ - id: quality
79
+ pass_condition: "The artifact is good."
80
+ ```
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ """Generate focused-loop packs from preset specs.
5
+
6
+ Preset files use YAML-compatible JSON so this tool stays stdlib-only while the
7
+ generated loop spec remains readable YAML.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import copy
13
+ import json
14
+ import re
15
+ import shutil
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ HERE = Path(__file__).resolve().parent
21
+ SKILL_ROOT = HERE.parent
22
+ PRESETS_DIR = SKILL_ROOT / "presets"
23
+
24
+ IDENTIFIER_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
25
+
26
+
27
+ class LoopBuilderError(RuntimeError):
28
+ pass
29
+
30
+
31
+ def slugify(value: str) -> str:
32
+ slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
33
+ slug = re.sub(r"-+", "-", slug)
34
+ if not slug:
35
+ raise LoopBuilderError("loop id cannot be empty")
36
+ return slug
37
+
38
+
39
+ def require_identifier(value: str, *, field: str) -> str:
40
+ slug = slugify(value)
41
+ if not IDENTIFIER_RE.match(slug):
42
+ raise LoopBuilderError(f"{field} must be kebab-case: {value!r}")
43
+ return slug
44
+
45
+
46
+ def preset_paths() -> list[Path]:
47
+ return sorted(PRESETS_DIR.glob("*.yaml"))
48
+
49
+
50
+ def available_presets() -> list[str]:
51
+ return [path.stem for path in preset_paths()]
52
+
53
+
54
+ def load_preset(name: str) -> dict[str, Any]:
55
+ preset_id = require_identifier(name, field="preset")
56
+ path = PRESETS_DIR / f"{preset_id}.yaml"
57
+ if not path.is_file():
58
+ choices = ", ".join(available_presets()) or "(none)"
59
+ raise LoopBuilderError(f"unknown preset {preset_id!r}; available: {choices}")
60
+ try:
61
+ data = json.loads(path.read_text(encoding="utf-8"))
62
+ except json.JSONDecodeError as exc:
63
+ raise LoopBuilderError(f"invalid preset JSON in {path}: {exc}") from exc
64
+ validate_preset(data, source=path)
65
+ return data
66
+
67
+
68
+ def validate_preset(data: dict[str, Any], *, source: Path | None = None) -> None:
69
+ required = ("id", "title", "summary", "inputs", "outputs", "phases", "validators", "gates", "skill_chain", "learn")
70
+ missing = [key for key in required if key not in data]
71
+ if missing:
72
+ label = str(source) if source else data.get("id", "<preset>")
73
+ raise LoopBuilderError(f"{label} missing required fields: {', '.join(missing)}")
74
+ require_identifier(str(data["id"]), field="preset id")
75
+ if not isinstance(data["phases"], dict) or not data["phases"]:
76
+ raise LoopBuilderError(f"{data['id']} phases must be a non-empty object")
77
+ if not isinstance(data["validators"], list) or not data["validators"]:
78
+ raise LoopBuilderError(f"{data['id']} validators must be a non-empty list")
79
+ if not isinstance(data["skill_chain"], dict):
80
+ raise LoopBuilderError(f"{data['id']} skill_chain must be an object")
81
+
82
+
83
+ def scalar_to_yaml(value: Any) -> str:
84
+ if value is True:
85
+ return "true"
86
+ if value is False:
87
+ return "false"
88
+ if value is None:
89
+ return "null"
90
+ if isinstance(value, (int, float)):
91
+ return str(value)
92
+ return json.dumps(str(value), ensure_ascii=False)
93
+
94
+
95
+ def to_yaml(value: Any, indent: int = 0) -> str:
96
+ pad = " " * indent
97
+ if isinstance(value, dict):
98
+ if not value:
99
+ return pad + "{}"
100
+ lines: list[str] = []
101
+ for key, item in value.items():
102
+ if isinstance(item, (dict, list)):
103
+ if not item:
104
+ lines.append(f"{pad}{key}: {to_yaml(item, 0).strip()}")
105
+ else:
106
+ lines.append(f"{pad}{key}:")
107
+ lines.append(to_yaml(item, indent + 2))
108
+ else:
109
+ lines.append(f"{pad}{key}: {scalar_to_yaml(item)}")
110
+ return "\n".join(lines)
111
+ if isinstance(value, list):
112
+ if not value:
113
+ return pad + "[]"
114
+ lines = []
115
+ for item in value:
116
+ if isinstance(item, (dict, list)):
117
+ lines.append(f"{pad}-")
118
+ lines.append(to_yaml(item, indent + 2))
119
+ else:
120
+ lines.append(f"{pad}- {scalar_to_yaml(item)}")
121
+ return "\n".join(lines)
122
+ return pad + scalar_to_yaml(value)
123
+
124
+
125
+ def build_loop_spec(preset: dict[str, Any], *, loop_id: str, title: str | None, preset_name: str) -> dict[str, Any]:
126
+ spec = copy.deepcopy(preset)
127
+ spec["schema_version"] = 1
128
+ spec["id"] = loop_id
129
+ spec["source_preset"] = preset_name
130
+ if title:
131
+ spec["title"] = title
132
+ return spec
133
+
134
+
135
+ def render_loop_yaml(spec: dict[str, Any]) -> str:
136
+ header = [
137
+ "# Generated focused-loop spec.",
138
+ "# Edit the artifact contract, validators, and skill_chain before using on high-stakes work.",
139
+ "",
140
+ ]
141
+ return "\n".join(header) + to_yaml(spec) + "\n"
142
+
143
+
144
+ def render_rubric(spec: dict[str, Any]) -> str:
145
+ lines = [
146
+ f"# {spec['title']} Rubric",
147
+ "",
148
+ spec["summary"],
149
+ "",
150
+ "## Binary Validators",
151
+ "",
152
+ "| Validator | Pass Condition | Method |",
153
+ "|---|---|---|",
154
+ ]
155
+ for validator in spec["validators"]:
156
+ lines.append(
157
+ f"| `{validator['id']}` | {validator['pass_condition']} | {validator.get('method', 'review')} |"
158
+ )
159
+ lines.extend(
160
+ [
161
+ "",
162
+ "## Confirmation Gates",
163
+ "",
164
+ ]
165
+ )
166
+ for gate_name, gate_items in spec.get("gates", {}).items():
167
+ lines.append(f"### {gate_name}")
168
+ lines.append("")
169
+ for item in gate_items:
170
+ lines.append(f"- `{item}`")
171
+ lines.append("")
172
+ return "\n".join(lines).rstrip() + "\n"
173
+
174
+
175
+ def render_report_template(spec: dict[str, Any]) -> str:
176
+ validator_rows = "\n".join(f"| `{item['id']}` | pass/fail | evidence |" for item in spec["validators"])
177
+ outputs = "\n".join(f"- {item}" for item in spec["outputs"])
178
+ return f"""# {spec['title']} Report
179
+
180
+ ## Bottom Line
181
+
182
+ [One sentence: what changed, what decision/artifact is ready, and what remains open.]
183
+
184
+ ## Outputs
185
+
186
+ {outputs}
187
+
188
+ ## Evidence
189
+
190
+ - [source path / transcript timestamp / row or slide reference]
191
+
192
+ ## Validator Results
193
+
194
+ | Validator | Verdict | Evidence |
195
+ |---|---|---|
196
+ {validator_rows}
197
+
198
+ ## Gates
199
+
200
+ - External send/publish: [clear/confirm]
201
+ - Sensitive data exposure: [clear/confirm]
202
+ - Irreversible source-of-truth change: [clear/confirm]
203
+
204
+ ## Learn
205
+
206
+ - Reusable artifact:
207
+ - Source quirks:
208
+ - Rubric failures:
209
+ """
210
+
211
+
212
+ def render_validator(spec: dict[str, Any]) -> str:
213
+ required_tokens = ["schema_version", "id", "phases", "validators", "gates", "skill_chain", "learn"]
214
+ token_literal = repr(required_tokens)
215
+ return f'''#!/usr/bin/env python3
216
+ # SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
217
+ # SPDX-License-Identifier: Apache-2.0
218
+ """Basic generated validator for the {spec["id"]} loop pack."""
219
+ from __future__ import annotations
220
+
221
+ import sys
222
+ from pathlib import Path
223
+
224
+
225
+ ROOT = Path(__file__).resolve().parents[1]
226
+ REQUIRED_FILES = [
227
+ ROOT / "loop.yaml",
228
+ ROOT / "rubric.md",
229
+ ROOT / "templates" / "report.md",
230
+ ]
231
+ REQUIRED_TOKENS = {token_literal}
232
+
233
+
234
+ def main() -> int:
235
+ missing = [str(path.relative_to(ROOT)) for path in REQUIRED_FILES if not path.is_file()]
236
+ if missing:
237
+ print("missing required files: " + ", ".join(missing), file=sys.stderr)
238
+ return 1
239
+ text = (ROOT / "loop.yaml").read_text(encoding="utf-8")
240
+ absent = [token for token in REQUIRED_TOKENS if token + ":" not in text]
241
+ if absent:
242
+ print("loop.yaml missing required fields: " + ", ".join(absent), file=sys.stderr)
243
+ return 1
244
+ print("loop pack ok: " + str(ROOT))
245
+ return 0
246
+
247
+
248
+ if __name__ == "__main__":
249
+ raise SystemExit(main())
250
+ '''
251
+
252
+
253
+ def write_file(path: Path, content: str, *, executable: bool = False) -> None:
254
+ path.parent.mkdir(parents=True, exist_ok=True)
255
+ path.write_text(content, encoding="utf-8")
256
+ if executable:
257
+ path.chmod(0o755)
258
+
259
+
260
+ def create_loop(
261
+ *,
262
+ loop_id_raw: str,
263
+ preset_name: str,
264
+ output: Path | None,
265
+ title: str | None,
266
+ force: bool,
267
+ ) -> Path:
268
+ loop_id = require_identifier(loop_id_raw, field="loop id")
269
+ preset_id = require_identifier(preset_name, field="preset")
270
+ preset = load_preset(preset_id)
271
+ spec = build_loop_spec(preset, loop_id=loop_id, title=title, preset_name=preset_id)
272
+ target = output.expanduser().resolve() if output else (Path.cwd() / ".build-loop" / "loops" / loop_id).resolve()
273
+ if target.exists():
274
+ if not force:
275
+ raise LoopBuilderError(f"target already exists: {target}; pass --force to replace")
276
+ shutil.rmtree(target)
277
+ write_file(target / "loop.yaml", render_loop_yaml(spec))
278
+ write_file(target / "rubric.md", render_rubric(spec))
279
+ write_file(target / "templates" / "report.md", render_report_template(spec))
280
+ write_file(target / "validators" / "validate_loop.py", render_validator(spec), executable=True)
281
+ write_file(
282
+ target / "README.md",
283
+ f"# {spec['title']}\n\nGenerated from `{preset_id}`. Start with `loop.yaml`, then run `python3 validators/validate_loop.py`.\n",
284
+ )
285
+ return target
286
+
287
+
288
+ def cmd_list(_args: argparse.Namespace) -> int:
289
+ for name in available_presets():
290
+ preset = load_preset(name)
291
+ print(f"{name}\t{preset['title']}")
292
+ return 0
293
+
294
+
295
+ def cmd_inspect(args: argparse.Namespace) -> int:
296
+ preset = load_preset(args.preset)
297
+ print(render_loop_yaml(build_loop_spec(preset, loop_id=preset["id"], title=None, preset_name=preset["id"])))
298
+ return 0
299
+
300
+
301
+ def cmd_create(args: argparse.Namespace) -> int:
302
+ target = create_loop(
303
+ loop_id_raw=args.loop_id,
304
+ preset_name=args.preset,
305
+ output=Path(args.output) if args.output else None,
306
+ title=args.title,
307
+ force=args.force,
308
+ )
309
+ print(f"created loop pack: {target}")
310
+ return 0
311
+
312
+
313
+ def build_parser() -> argparse.ArgumentParser:
314
+ parser = argparse.ArgumentParser(description=__doc__)
315
+ sub = parser.add_subparsers(dest="command", required=True)
316
+
317
+ list_parser = sub.add_parser("list", help="List available loop presets.")
318
+ list_parser.set_defaults(func=cmd_list)
319
+
320
+ inspect_parser = sub.add_parser("inspect", help="Print a preset as generated loop YAML.")
321
+ inspect_parser.add_argument("preset")
322
+ inspect_parser.set_defaults(func=cmd_inspect)
323
+
324
+ create_parser = sub.add_parser("create", help="Create a focused-loop pack.")
325
+ create_parser.add_argument("loop_id")
326
+ create_parser.add_argument("--preset", required=True, help="Preset name from `list`.")
327
+ create_parser.add_argument("--output", help="Target directory. Defaults to .build-loop/loops/<loop-id>.")
328
+ create_parser.add_argument("--title", help="Override generated title.")
329
+ create_parser.add_argument("--force", action="store_true", help="Replace an existing target directory.")
330
+ create_parser.set_defaults(func=cmd_create)
331
+
332
+ return parser
333
+
334
+
335
+ def main(argv: list[str] | None = None) -> int:
336
+ parser = build_parser()
337
+ args = parser.parse_args(argv)
338
+ try:
339
+ return int(args.func(args))
340
+ except LoopBuilderError as exc:
341
+ print(f"error: {exc}", file=sys.stderr)
342
+ return 1
343
+
344
+
345
+ if __name__ == "__main__":
346
+ raise SystemExit(main())