@surlymochan/evalid 0.0.0-stage → 0.3.1

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 (192) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +315 -2
  4. package/RELEASE_NOTES.md +102 -0
  5. package/atoms/registry.json +758 -0
  6. package/bin/xflow.js +7208 -0
  7. package/docs/adoption/README.md +128 -0
  8. package/docs/adoption/openflow-release-hardening.md +61 -0
  9. package/docs/benchmark-openflow-vs-gstack-superpowers-openspec-2026-05-25.md +81 -0
  10. package/docs/compatibility.md +52 -0
  11. package/docs/competitive-benchmark.md +108 -0
  12. package/docs/corps-operator-guide.md +75 -0
  13. package/docs/demo-proof.md +126 -0
  14. package/docs/examples-gallery.md +115 -0
  15. package/docs/fixtures/tracker-item.json +7 -0
  16. package/docs/goal-vs-codex.md +129 -0
  17. package/docs/gstack-comparison.md +88 -0
  18. package/docs/install-upgrade.md +133 -0
  19. package/docs/integrations.md +49 -0
  20. package/docs/launch-demo.md +123 -0
  21. package/docs/launch-dossier.md +180 -0
  22. package/docs/methodology.md +149 -0
  23. package/docs/npm-publish-handoff.md +83 -0
  24. package/docs/openspec-migration.md +106 -0
  25. package/docs/public-benchmark.md +68 -0
  26. package/docs/public-release.md +145 -0
  27. package/docs/quality-assessment.md +139 -0
  28. package/docs/quickstart.md +156 -0
  29. package/docs/reviewer-guide.md +43 -0
  30. package/docs/spec-kit-benchmark.md +73 -0
  31. package/docs/super-assistant-comparison.md +106 -0
  32. package/docs/superpowers-comparison.md +82 -0
  33. package/docs/team-adoption.md +123 -0
  34. package/docs/tooling-matrix.md +91 -0
  35. package/docs/walkthrough.md +76 -0
  36. package/docs/workflow-manual.md +1125 -0
  37. package/package.json +83 -4
  38. package/prompts/pencil-draft.md +46 -0
  39. package/prompts/pencil-refine.md +46 -0
  40. package/prompts/phase-artifact.md +24 -0
  41. package/prompts/phase-execution.md +32 -0
  42. package/schemas/competitor-reconstruction-contract.schema.json +91 -0
  43. package/schemas/llm-design-review-aggregate.schema.json +83 -0
  44. package/schemas/visual-benchmark.schema.json +978 -0
  45. package/schemas/workflow.schema.json +100 -0
  46. package/scripts/run-tests.mjs +23 -0
  47. package/scripts/takein_digest.py +139 -0
  48. package/specs/README.md +118 -0
  49. package/specs/architecture.md +36 -0
  50. package/specs/product.md +33 -0
  51. package/specs/workflow.md +349 -0
  52. package/src/agent_team/atoms/codex-app-bridge.js +8 -0
  53. package/src/agent_team/atoms/design/competitor-reconstruction-review.js +44 -0
  54. package/src/agent_team/atoms/design/contract-freeze.js +8 -0
  55. package/src/agent_team/atoms/design/generation-contract.js +442 -0
  56. package/src/agent_team/atoms/design/reconstruction-pack.js +246 -0
  57. package/src/agent_team/atoms/design/reference-surface-lock.js +49 -0
  58. package/src/agent_team/atoms/patch-challenge.js +19 -0
  59. package/src/agent_team/atoms/pencil/accept.js +9 -0
  60. package/src/agent_team/atoms/pencil/draft.js +365 -0
  61. package/src/agent_team/atoms/pencil/refine.js +229 -0
  62. package/src/agent_team/atoms/phase-run-dispatch.js +8 -0
  63. package/src/agent_team/atoms/team-run.js +380 -0
  64. package/src/agent_team/atoms/visual/aesthetic-review.js +231 -0
  65. package/src/agent_team/atoms/visual/benchmark-repair-plan.js +291 -0
  66. package/src/agent_team/atoms/visual/benchmark.js +1652 -0
  67. package/src/agent_team/atoms/visual/design-selection.js +23 -0
  68. package/src/agent_team/atoms/visual/direction-synthesis.js +124 -0
  69. package/src/agent_team/atoms/visual/dom-rect-export.js +129 -0
  70. package/src/agent_team/atoms/visual/image-diff.js +186 -0
  71. package/src/agent_team/atoms/visual/layout-competition.js +8 -0
  72. package/src/agent_team/atoms/visual/page-evidence-capture.js +806 -0
  73. package/src/agent_team/atoms/visual/playwright-runtime.js +8 -0
  74. package/src/agent_team/atoms/visual/review-aggregate.js +87 -0
  75. package/src/agent_team/atoms/visual/review.js +77 -0
  76. package/src/agent_team/atoms/visual/visual-report.js +197 -0
  77. package/src/agent_team/atoms/visual/visual-token-export.js +417 -0
  78. package/src/agent_team/common.js +311 -0
  79. package/src/core/atoms/agent-invoke.js +684 -0
  80. package/src/core/atoms/event/ingest.js +30 -0
  81. package/src/core/atoms/experience/digest.js +55 -0
  82. package/src/core/atoms/gate/artifact-verify.js +83 -0
  83. package/src/core/atoms/gate/competitor-contract-validate.js +119 -0
  84. package/src/core/atoms/gate/evidence-collect.js +64 -0
  85. package/src/core/atoms/gate/payload-from-phase-run.js +78 -0
  86. package/src/core/atoms/gate/visual-benchmark-validate.js +183 -0
  87. package/src/core/atoms/gate/workbench.js +79 -0
  88. package/src/core/atoms/intake/classify.js +15 -0
  89. package/src/core/atoms/intake/design-skill-select.js +16 -0
  90. package/src/core/atoms/intake/preview-or-refine.js +19 -0
  91. package/src/core/atoms/intake/recommend.js +20 -0
  92. package/src/core/atoms/intake/research-same-category.js +19 -0
  93. package/src/core/atoms/intervention/create.js +30 -0
  94. package/src/core/atoms/memory/manage.js +27 -0
  95. package/src/core/atoms/memory/mount.js +13 -0
  96. package/src/core/atoms/mission/create.js +19 -0
  97. package/src/core/atoms/mission/list.js +14 -0
  98. package/src/core/atoms/mission/show.js +26 -0
  99. package/src/core/atoms/notify/checkpoint.js +46 -0
  100. package/src/core/atoms/phase-run/complete.js +42 -0
  101. package/src/core/atoms/phase-run/fail.js +40 -0
  102. package/src/core/atoms/phase-run/persist.js +39 -0
  103. package/src/core/atoms/phase-run/start.js +30 -0
  104. package/src/core/atoms/plugin/run.js +76 -0
  105. package/src/core/atoms/policy/gate.js +90 -0
  106. package/src/core/atoms/policy/resolve.js +11 -0
  107. package/src/core/atoms/storage/sync.js +57 -0
  108. package/src/core/atoms/workflow/advance.js +25 -0
  109. package/src/core/atoms/workflow/guard.js +127 -0
  110. package/src/core/atoms/workflow/state-load.js +37 -0
  111. package/src/core/bindings.js +37 -0
  112. package/src/core/change-artifacts.js +134 -0
  113. package/src/core/competitive-score.js +265 -0
  114. package/src/core/concurrency-policy.js +35 -0
  115. package/src/core/execution-log.js +108 -0
  116. package/src/core/intake-recommendation.js +134 -0
  117. package/src/core/memory-state.js +62 -0
  118. package/src/core/mission-state.js +780 -0
  119. package/src/core/pending-gates.js +66 -0
  120. package/src/core/phase-cost.js +116 -0
  121. package/src/core/phase-run-read.js +27 -0
  122. package/src/core/policy-overlay.js +180 -0
  123. package/src/core/state-store.js +201 -0
  124. package/src/core/workflow-executor.js +998 -0
  125. package/src/core/workflow-integrity.js +111 -0
  126. package/src/core/workflow-loader.js +163 -0
  127. package/src/core/workflow-rerun.js +84 -0
  128. package/src/core/workflow-state.js +119 -0
  129. package/src/server.js +1307 -0
  130. package/workflows/corps.yaml +479 -0
  131. package/workflows/yolo.yaml +233 -0
  132. package/xflow/README.md +76 -0
  133. package/xflow/aha/SKILL.md +82 -0
  134. package/xflow/aha/scripts/append_aha.py +50 -0
  135. package/xflow/archive/scripts/archive_change.py +338 -0
  136. package/xflow/archive/templates/merge-design.md +54 -0
  137. package/xflow/archive/templates/merge-workflow.md +26 -0
  138. package/xflow/atoms/__init__.py +6 -0
  139. package/xflow/atoms/a1_issue_create_or_patch.py +70 -0
  140. package/xflow/atoms/a2_branch_create_and_link.py +42 -0
  141. package/xflow/atoms/a3_repo_doctor.py +11 -0
  142. package/xflow/atoms/a4_project_set_status.py +11 -0
  143. package/xflow/atoms/a5_archive_commit_push_close.py +11 -0
  144. package/xflow/atoms/a6_pr_create.py +72 -0
  145. package/xflow/atoms/b1_change_scaffold.py +11 -0
  146. package/xflow/atoms/b2_status_read.py +16 -0
  147. package/xflow/atoms/b2b_status_write.py +20 -0
  148. package/xflow/atoms/b3_status_transition.py +27 -0
  149. package/xflow/atoms/b4_handoff_scaffold_or_refresh.py +11 -0
  150. package/xflow/atoms/b5_aha_append.py +11 -0
  151. package/xflow/atoms/b6_takein_digest.py +11 -0
  152. package/xflow/atoms/b6b_lookback_digest.py +11 -0
  153. package/xflow/atoms/b7_change_mission_bind.py +43 -0
  154. package/xflow/atoms/c1_mem.py +54 -0
  155. package/xflow/atoms/e6_gate_local_precheck.py +23 -0
  156. package/xflow/atoms/h1_design_lite_gate.py +74 -0
  157. package/xflow/atoms/i6a_tdd_run.py +104 -0
  158. package/xflow/atoms/i6b_tdd_proof_validate.py +165 -0
  159. package/xflow/atoms/i6c_tdd_quality_review.py +357 -0
  160. package/xflow/atoms/j1_tests_run.py +129 -0
  161. package/xflow/atoms/j4a_spec_consistency_structural.py +105 -0
  162. package/xflow/atoms/j4b_spec_delta_review.py +316 -0
  163. package/xflow/atoms/j4c_openspec_migration_map.py +121 -0
  164. package/xflow/atoms/j4d_spec_clarify_gate.py +373 -0
  165. package/xflow/atoms/j6_cr_review.py +443 -0
  166. package/xflow/atoms/k1_artifacts_complete_check.py +39 -0
  167. package/xflow/atoms/k2_merge_snippets_apply.py +11 -0
  168. package/xflow/atoms/k3_handoff_refresh.py +14 -0
  169. package/xflow/atoms/k4_mem_lesson_persist.py +65 -0
  170. package/xflow/atoms/k5_archive_publish.py +21 -0
  171. package/xflow/atoms/k6_aha_merge.py +42 -0
  172. package/xflow/atoms/tdd_lib.py +747 -0
  173. package/xflow/corps/SKILL.md +109 -0
  174. package/xflow/exec/scripts/set_issue_in_progress.py +159 -0
  175. package/xflow/goal/SKILL.md +150 -0
  176. package/xflow/handoff/SKILL.md +95 -0
  177. package/xflow/handoff/scripts/scaffold_handoff.py +304 -0
  178. package/xflow/lookback/SKILL.md +66 -0
  179. package/xflow/lookback/scripts/lookback_digest.py +46 -0
  180. package/xflow/openissue/scripts/open_issue_flow.py +438 -0
  181. package/xflow/plan/SKILL.md +38 -0
  182. package/xflow/ralph/SKILL.md +123 -0
  183. package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
  184. package/xflow/scripts/common.py +480 -0
  185. package/xflow/scripts/doctor.py +278 -0
  186. package/xflow/scripts/sync_installed_xflow_skill.sh +134 -0
  187. package/xflow/takein/SKILL.md +81 -0
  188. package/xflow/takein/scripts/takein_digest.py +88 -0
  189. package/xflow/yolo/SKILL.md +66 -0
  190. package/xflow/yolo/scripts/scaffold_specs_change.py +673 -0
  191. package/xflow/yolo/scripts/yolo_gate.py +312 -0
  192. package/xflow/yolo/templates/DESIGN.md +179 -0
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.path.append(str(Path(__file__).resolve().parents[2] / "scripts"))
10
+
11
+ from doctor import run_doctor_checks # noqa: E402
12
+ from common import ( # noqa: E402
13
+ assert_stage_transition_allowed,
14
+ assert_status_transition_allowed,
15
+ discover_project_from_issue,
16
+ find_git_root,
17
+ get_remote_url,
18
+ gh_json,
19
+ load_status,
20
+ record_stage_transition,
21
+ record_value_transition,
22
+ local_branch_exists,
23
+ maybe_relative,
24
+ now_iso,
25
+ read_text,
26
+ remote_branch_exists,
27
+ run,
28
+ run_stdout,
29
+ save_status,
30
+ slugify,
31
+ upsert_issue_comment,
32
+ write_text,
33
+ )
34
+
35
+
36
+ DEFAULT_ISSUE_REPO = os.environ.get("XFLOW_DEFAULT_ISSUE_REPO", "").strip()
37
+ PLACEHOLDER_ISSUE_REPO = "owner/internal-tracker"
38
+
39
+
40
+ def parse_args() -> argparse.Namespace:
41
+ parser = argparse.ArgumentParser(description="Open or normalize a GitHub issue, create a linked branch, and update Superflow change metadata.")
42
+ parser.add_argument("--project-root", default=".", help="Project root where specs/changes lives.")
43
+ parser.add_argument("--repo", help="Target repository in owner/name form. Defaults to .as-xflow/config.json issue_routing.repo.")
44
+ parser.add_argument("--change-id", required=True, help="Existing or new Superflow change id.")
45
+ parser.add_argument("--title", help="Issue title. Required when creating a new issue.")
46
+ parser.add_argument("--body", help="Issue body text.")
47
+ parser.add_argument("--body-file", help="Path to a file containing the issue body.")
48
+ parser.add_argument("--issue-number", type=int, help="Reuse and optionally update an existing issue instead of creating a new one.")
49
+ parser.add_argument("--change-type", choices=["backend", "frontend", "full-stack", "infrastructure", "docs"], default="backend")
50
+ parser.add_argument("--checkout-path", help="Local checkout path for the target repo.")
51
+ parser.add_argument("--base-branch", help="Base branch to branch from. Defaults to the repo default branch.")
52
+ parser.add_argument("--branch-name", help="Override the branch name. Defaults to issue-<number>-<change-id>.")
53
+ parser.add_argument("--with-design", action="store_true", help="Create DESIGN.md if the scaffold needs it.")
54
+ parser.add_argument("--skip-doctor-postcheck", action="store_true", help="Skip the automatic doctor postcheck after issue/branch setup.")
55
+ parser.add_argument("--json", action="store_true", help="Print machine-readable JSON output.")
56
+ return parser.parse_args()
57
+
58
+
59
+ def load_project_config(project_root: str | Path) -> dict[str, object]:
60
+ config_path = Path(project_root).expanduser().resolve() / ".as-xflow" / "config.json"
61
+ if not config_path.exists():
62
+ return {}
63
+ return json.loads(config_path.read_text(encoding="utf-8"))
64
+
65
+
66
+ def configured_issue_repo(project_root: str | Path) -> str | None:
67
+ config = load_project_config(project_root)
68
+ issue_routing = config.get("issue_routing")
69
+ if isinstance(issue_routing, dict):
70
+ repo = issue_routing.get("repo")
71
+ if isinstance(repo, str) and repo.strip():
72
+ return repo.strip()
73
+ return None
74
+
75
+
76
+ def resolve_issue_repo(args: argparse.Namespace, checkout_path: Path | None = None) -> str:
77
+ repo = args.repo or configured_issue_repo(args.project_root) or DEFAULT_ISSUE_REPO
78
+ if repo:
79
+ return repo
80
+ if checkout_path is not None:
81
+ return get_checkout_repo(checkout_path)
82
+ raise SystemExit("Issue repo is required. Pass --repo, set .as-xflow/config.json issue_routing.repo, or run from a GitHub checkout.")
83
+
84
+
85
+ def load_issue_body(args: argparse.Namespace) -> str | None:
86
+ if args.body_file:
87
+ return Path(args.body_file).expanduser().read_text(encoding="utf-8")
88
+ return args.body
89
+
90
+
91
+ def parse_repo_from_remote_url(remote_url: str) -> str | None:
92
+ match = re.search(r"github\.com[:/](?P<repo>[^/]+/[^/.]+?)(?:\.git)?$", remote_url.strip(), flags=re.IGNORECASE)
93
+ if not match:
94
+ return None
95
+ return str(match.group("repo"))
96
+
97
+
98
+ def resolve_code_checkout(preferred: str | None, project_root: str | Path) -> Path:
99
+ if preferred:
100
+ checkout_path = Path(preferred).expanduser().resolve()
101
+ if not checkout_path.exists():
102
+ raise SystemExit(f"Checkout path does not exist: {checkout_path}")
103
+ return checkout_path
104
+ return find_git_root(project_root)
105
+
106
+
107
+ def get_checkout_repo(checkout_path: Path) -> str:
108
+ remote_url = get_remote_url(checkout_path)
109
+ repo = parse_repo_from_remote_url(remote_url)
110
+ if not repo:
111
+ raise SystemExit(f"Unable to derive owner/name from checkout remote: {remote_url!r}")
112
+ return repo
113
+
114
+
115
+ def get_checkout_default_branch(checkout_path: Path) -> str:
116
+ try:
117
+ symbolic_ref = run_stdout(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], cwd=checkout_path)
118
+ if symbolic_ref:
119
+ return symbolic_ref.rsplit("/", 1)[-1]
120
+ except subprocess.CalledProcessError:
121
+ pass
122
+ try:
123
+ remote_show = run_stdout(["git", "remote", "show", "origin"], cwd=checkout_path)
124
+ for line in remote_show.splitlines():
125
+ stripped = line.strip()
126
+ if stripped.startswith("HEAD branch:"):
127
+ branch_name = stripped.split(":", 1)[1].strip()
128
+ if branch_name:
129
+ return branch_name
130
+ except subprocess.CalledProcessError:
131
+ pass
132
+ current_branch = run_stdout(["git", "branch", "--show-current"], cwd=checkout_path)
133
+ return current_branch or "main"
134
+
135
+
136
+ def enforce_evalid_openissue_contract(repo: str, title: str | None, body: str | None, checkout_path: Path) -> None:
137
+ if repo == PLACEHOLDER_ISSUE_REPO:
138
+ raise SystemExit("Configure a real issue repo with --repo or .as-xflow/config.json issue_routing.repo before running openissue.")
139
+ if not title:
140
+ raise SystemExit("evalid openissue requires a non-empty issue title.")
141
+ if not body:
142
+ raise SystemExit("evalid openissue requires a non-empty issue body. Pass --body or --body-file.")
143
+ if not checkout_path.exists():
144
+ raise SystemExit(f"Checkout path does not exist: {checkout_path}")
145
+
146
+
147
+ def extract_section(body: str, headings: list[str]) -> str:
148
+ if not body:
149
+ return "-"
150
+ lines = body.splitlines()
151
+ normalized = [heading.lower() for heading in headings]
152
+ capture = False
153
+ collected: list[str] = []
154
+ for line in lines:
155
+ stripped = line.strip()
156
+ if stripped.startswith("#"):
157
+ heading_text = stripped.lstrip("#").strip().lower()
158
+ if any(heading_text.startswith(heading) for heading in normalized):
159
+ capture = True
160
+ collected = []
161
+ continue
162
+ if capture:
163
+ break
164
+ if capture:
165
+ collected.append(line)
166
+ text = "\n".join(collected).strip()
167
+ return text or "-"
168
+
169
+
170
+ def is_scaffold_placeholder_line(line: str) -> bool:
171
+ stripped = line.strip()
172
+ if not stripped:
173
+ return True
174
+ if stripped.startswith("#"):
175
+ return True
176
+ if stripped in {"-", "1.", "2.", "3.", "```", "```bash"}:
177
+ return True
178
+ if stripped.startswith("# Fill in the smallest proof commands"):
179
+ return True
180
+ return stripped in {
181
+ "- Visual tone:",
182
+ "- Key differentiator:",
183
+ "- Reference brand (if any):",
184
+ "- Key specifications to verify:",
185
+ }
186
+
187
+
188
+ def should_hydrate_doc(path: Path) -> bool:
189
+ if not path.exists():
190
+ return True
191
+ content = read_text(path)
192
+ if not content.strip():
193
+ return True
194
+ return all(is_scaffold_placeholder_line(line) for line in content.splitlines())
195
+
196
+
197
+ def hydrate_change_docs(project_root: str | Path, change_id: str, issue_title: str, issue_body: str | None, branch_name: str) -> None:
198
+ change_root = Path(project_root).expanduser().resolve() / "specs" / "changes" / change_id
199
+ proposal_path = change_root / "proposal.md"
200
+ plan_path = change_root / "plan.md"
201
+ issue_path = change_root / "issue.md"
202
+ issue_body = issue_body or ""
203
+
204
+ objective = issue_title.strip() or change_id
205
+ problem = extract_section(issue_body, ["problem", "context", "background"])
206
+ expected = extract_section(issue_body, ["expected outcome", "outcome"])
207
+ scope = extract_section(issue_body, ["scope", "in scope"])
208
+ non_goals = extract_section(issue_body, ["non-goals", "non goals", "out of scope"])
209
+ acceptance = extract_section(issue_body, ["acceptance criteria", "acceptance"])
210
+
211
+ proposal = f"""# Proposal
212
+
213
+ ## Objective
214
+
215
+ - {objective}
216
+
217
+ ## Problem / Context
218
+
219
+ {problem}
220
+
221
+ ## Chosen Path
222
+
223
+ - Track execution through issue branch `{branch_name}`
224
+ - Use the linked GitHub issue as the external coordination anchor
225
+
226
+ ## Rejected Paths
227
+
228
+ - Leave issue and branch unlinked
229
+ - Execute without change-scoped artifacts
230
+
231
+ ## In Scope
232
+
233
+ {scope}
234
+
235
+ ## Out of Scope
236
+
237
+ {non_goals}
238
+
239
+ ## Acceptance Intent
240
+
241
+ {acceptance if acceptance != '-' else expected}
242
+ """
243
+
244
+ plan = f"""# Plan
245
+
246
+ ## Target Outcome
247
+
248
+ - {objective}
249
+
250
+ ## Chosen Path
251
+
252
+ - Work on linked branch `{branch_name}`
253
+ - Keep implementation aligned with the GitHub issue and change artifacts
254
+
255
+ ## In Scope
256
+
257
+ {scope}
258
+
259
+ ## Out of Scope
260
+
261
+ {non_goals}
262
+
263
+ ## Task Slices
264
+
265
+ 1. Finalize frozen scope against the issue
266
+ 2. Implement on the linked branch
267
+ 3. Verify and prepare archive artifacts
268
+
269
+ ## Verification Commands
270
+
271
+ ```bash
272
+ # Fill in the smallest proof commands for this change
273
+ ```
274
+
275
+ ## Main Risk
276
+
277
+ - Scope drift between issue, proposal, and implementation
278
+ """
279
+
280
+ if should_hydrate_doc(proposal_path):
281
+ write_text(proposal_path, proposal.rstrip() + "\n")
282
+ if should_hydrate_doc(plan_path):
283
+ write_text(plan_path, plan.rstrip() + "\n")
284
+ write_text(issue_path, f"# {objective}\n\n{issue_body.strip()}\n")
285
+
286
+
287
+ def scaffold_change_workspace(args: argparse.Namespace) -> None:
288
+ script_path = Path(__file__).resolve().parents[2] / "yolo" / "scripts" / "scaffold_specs_change.py"
289
+ cmd = [
290
+ "python3",
291
+ str(script_path),
292
+ "--project-root",
293
+ str(Path(args.project_root).expanduser().resolve()),
294
+ "--change-id",
295
+ args.change_id,
296
+ "--title",
297
+ args.title or args.change_id,
298
+ "--change-type",
299
+ args.change_type,
300
+ ]
301
+ if args.with_design or args.change_type in {"frontend", "full-stack"}:
302
+ cmd.append("--with-design")
303
+ run(cmd)
304
+
305
+
306
+ def create_or_update_issue(repo: str, title: str | None, body: str | None, issue_number: int | None) -> dict[str, object]:
307
+ if issue_number is None:
308
+ if not title:
309
+ raise SystemExit("--title is required when creating a new issue")
310
+ if not body:
311
+ raise SystemExit("Issue body is required when creating a new issue. Pass --body or --body-file.")
312
+ cmd = ["gh", "issue", "create", "--repo", repo, "--title", title, "--body", body]
313
+ issue_url = run_stdout(cmd)
314
+ return gh_json(["issue", "view", issue_url, "--repo", repo, "--json", "number,url,title"])
315
+
316
+ edit_cmd = ["gh", "issue", "edit", str(issue_number), "--repo", repo]
317
+ mutated = False
318
+ if title:
319
+ edit_cmd.extend(["--title", title])
320
+ mutated = True
321
+ if body is not None:
322
+ edit_cmd.extend(["--body", body])
323
+ mutated = True
324
+ if mutated:
325
+ run(edit_cmd)
326
+ return gh_json(["issue", "view", str(issue_number), "--repo", repo, "--json", "number,url,title"])
327
+
328
+
329
+ def ensure_linked_branch(issue_repo: str, issue_number: int, branch_name: str, base_branch: str, checkout_path: Path) -> dict[str, object]:
330
+ run(["git", "fetch", "origin", base_branch], cwd=checkout_path)
331
+
332
+ created = False
333
+ if local_branch_exists(checkout_path, branch_name):
334
+ run(["git", "checkout", branch_name], cwd=checkout_path)
335
+ elif remote_branch_exists(checkout_path, branch_name):
336
+ run(["git", "checkout", "-B", branch_name, "--track", f"origin/{branch_name}"], cwd=checkout_path)
337
+ else:
338
+ run(["git", "checkout", "-B", branch_name, f"origin/{base_branch}"], cwd=checkout_path)
339
+ created = True
340
+
341
+ if not remote_branch_exists(checkout_path, branch_name):
342
+ run(["git", "push", "-u", "origin", branch_name], cwd=checkout_path)
343
+
344
+ code_repo = get_checkout_repo(checkout_path)
345
+ marker = "<!-- xflow:branch-link -->"
346
+ upsert_issue_comment(
347
+ issue_repo,
348
+ issue_number,
349
+ marker,
350
+ f"Linked branch: `{branch_name}`\n\nCode repo: `{code_repo}`\nBase branch: `{base_branch}`\nCheckout: `{checkout_path}`",
351
+ )
352
+ return {
353
+ "branch_name": branch_name,
354
+ "base_branch": base_branch,
355
+ "created": created,
356
+ "code_repo": code_repo,
357
+ }
358
+
359
+
360
+ def main() -> int:
361
+ args = parse_args()
362
+ scaffold_change_workspace(args)
363
+ status_path, status = load_status(args.project_root, args.change_id)
364
+ assert_stage_transition_allowed(status.get("current_stage"), "plan", current_status=status.get("status"))
365
+ assert_status_transition_allowed(status.get("status"), "active")
366
+
367
+ issue_body = load_issue_body(args)
368
+ checkout_path = resolve_code_checkout(args.checkout_path, args.project_root)
369
+ issue_repo = resolve_issue_repo(args, checkout_path)
370
+ enforce_evalid_openissue_contract(issue_repo, args.title, issue_body, checkout_path)
371
+ issue = create_or_update_issue(issue_repo, args.title, issue_body, args.issue_number)
372
+ issue_number = int(issue["number"])
373
+ issue_url = str(issue["url"])
374
+ base_branch = args.base_branch or get_checkout_default_branch(checkout_path)
375
+ branch_name = args.branch_name or f"issue-{issue_number}-{slugify(args.change_id)}"
376
+ branch_result = ensure_linked_branch(issue_repo, issue_number, branch_name, base_branch, checkout_path)
377
+ hydrate_change_docs(args.project_root, args.change_id, str(issue.get("title") or args.title or args.change_id), issue_body, branch_result["branch_name"])
378
+ discovered_project = discover_project_from_issue(issue_repo, issue_number)
379
+
380
+ record_value_transition(status, "status", "active", reason="openissue prepared the change for tracked execution")
381
+ record_stage_transition(status, "plan", reason="openissue prepared linked issue and branch")
382
+ status["archival_status"] = "not_ready"
383
+ status["target_repo"] = issue_repo
384
+ status["issue_repo"] = issue_repo
385
+ status["code_repo"] = branch_result["code_repo"]
386
+ status["issue_number"] = issue_number
387
+ status["issue_url"] = issue_url
388
+ status["issue_title"] = issue.get("title") or args.title or args.change_id
389
+ status["branch_name"] = branch_result["branch_name"]
390
+ status["base_branch"] = branch_result["base_branch"]
391
+ status["checkout_path"] = str(checkout_path)
392
+ status["next_action"] = "Continue with xflow:yolo or xflow:corps from the frozen plan."
393
+ status["issue_prepared_at"] = now_iso()
394
+ if discovered_project:
395
+ status["project_context"] = discovered_project
396
+ save_status(status_path, status)
397
+ doctor_result = None
398
+ if not args.skip_doctor_postcheck:
399
+ doctor_result = run_doctor_checks(
400
+ project_root=args.project_root,
401
+ change_id=args.change_id,
402
+ repo=issue_repo,
403
+ issue_number=issue_number,
404
+ branch_name=branch_result["branch_name"],
405
+ checkout_path_override=checkout_path,
406
+ )
407
+ if not doctor_result["ok"]:
408
+ raise SystemExit("Doctor postcheck failed after openissue setup. Run doctor.py for details.")
409
+
410
+ result = {
411
+ "change_id": args.change_id,
412
+ "repo": issue_repo,
413
+ "issue_repo": issue_repo,
414
+ "code_repo": branch_result["code_repo"],
415
+ "issue_number": issue_number,
416
+ "issue_url": issue_url,
417
+ "branch_name": branch_result["branch_name"],
418
+ "base_branch": branch_result["base_branch"],
419
+ "checkout_path": str(checkout_path),
420
+ "status_path": maybe_relative(status_path, base=args.project_root),
421
+ }
422
+ if discovered_project:
423
+ result["project_context"] = discovered_project
424
+ if doctor_result is not None:
425
+ result["doctor"] = {"ok": doctor_result["ok"], "hard_failures": doctor_result["hard_failures"], "warnings": doctor_result["warnings"]}
426
+ if args.json:
427
+ print(json.dumps(result, indent=2))
428
+ else:
429
+ print(f"Prepared change {args.change_id}")
430
+ print(f"Issue: {issue_repo}#{issue_number} {issue_url}")
431
+ print(f"Branch: {branch_result['branch_name']} (base: {branch_result['base_branch']})")
432
+ print(f"Checkout: {checkout_path}")
433
+ print(f"Status: {status_path}")
434
+ return 0
435
+
436
+
437
+ if __name__ == "__main__":
438
+ raise SystemExit(main())
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: xflow:plan
3
+ description: Track-neutral planning skill for xflow. Use it to turn an accepted change into a single implementation plan before choosing or running yolo/corps.
4
+ ---
5
+
6
+ # xflow:plan
7
+
8
+ Use this skill when the change needs a plan before execution.
9
+
10
+ ## Goal
11
+
12
+ Produce one reusable `plan.md` for the change. Do not produce `tasks.md`.
13
+ Do not decide whether the change is yolo or corps at planning time.
14
+
15
+ ## What the plan should cover
16
+
17
+ - active project goal from `.xflow/GOAL.md`, when present
18
+ - objective and expected outcome
19
+ - scope and explicit non-goals
20
+ - constraints and dependencies
21
+ - implementation approach
22
+ - verification strategy
23
+ - risks and failure modes
24
+
25
+ ## Reuse rule
26
+
27
+ If `specs/changes/<change-id>/plan.md` already exists and is still current,
28
+ reuse it instead of rewriting the same plan again.
29
+
30
+ If the file is missing or still a scaffold placeholder, create the reusable
31
+ plan once and leave routing to the execution workflow.
32
+
33
+ ## Boundary
34
+
35
+ - This skill does not open issues.
36
+ - This skill does not create branches.
37
+ - This skill does not commit or archive.
38
+ - This skill does not choose between yolo and corps.
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: xflow:ralph
3
+ description: "Codex-native Ralph/autopilot loop for xflow. Use when the user wants autonomous implementation until verified completion, self-healing from tests or review feedback, full-auto execution, or a Desktop-friendly alternative to OMX Ralph."
4
+ ---
5
+
6
+ # xflow:ralph
7
+
8
+ `xflow:ralph` is a Codex-native completion loop inspired by OMX `$ralph` and `$autopilot`, adapted for Codex Desktop, Codex CLI, and the existing xflow skill family. It does not depend on tmux, OMX hooks, HUD panes, or `omx_state`.
9
+
10
+ The contract: do not call work complete until the original request, later user feedback, implementation evidence, and verification evidence all agree.
11
+
12
+ ## Relation To xflow
13
+
14
+ - Use `xflow:plan` first when the user asks for planning, the task is broad, or a reusable plan is needed.
15
+ - Use `xflow:ralph` when the user wants a concrete task carried through implementation, verification, feedback repair, and completion audit.
16
+ - Use `xflow:yolo` or `xflow:corps` when the project has an explicit xflow runtime/change workflow and the user wants that governed track.
17
+ - `xflow:ralph` is a conversation-native loop; it should not pretend to execute the governed `xflow corps` runtime or produce corps proof artifacts.
18
+
19
+ ## Modes
20
+
21
+ Choose the smallest mode that satisfies the request.
22
+
23
+ | Mode | Trigger | Contract |
24
+ |---|---|---|
25
+ | `ralph` | Clear task or existing plan | Implement, verify, self-heal, audit, finish |
26
+ | `autopilot` | Vague-but-actionable feature, issue, PRD, or "full auto" | Plan, Ralph, review; loop back on review findings |
27
+ | `review-repair` | User provides test/review/feedback failures | Convert feedback into repair items, Ralph until clean |
28
+
29
+ ## Desktop Boundaries
30
+
31
+ - Use `update_plan` as the visible working ledger for substantial tasks.
32
+ - Keep exactly one plan item `in_progress`.
33
+ - Use local files only when they are useful project artifacts; do not create `.omx` files for Ralph state.
34
+ - Use Codex subagents only when the user explicitly asks for subagents, delegation, or parallel agent work.
35
+ - If Codex goal tools are visible, treat the active goal as binding scope and mark it complete only after the completion audit passes. If goal tools are not visible, continue without them.
36
+
37
+ ## Ralph Loop
38
+
39
+ 1. Intake the objective.
40
+ - Restate the concrete deliverables and acceptance criteria.
41
+ - Read project root `.xflow/GOAL.md` if present and treat it as directional context.
42
+ - Identify constraints, risky actions, likely files, and verification commands.
43
+ - Ask only if a missing answer would make the work unsafe or materially ambiguous.
44
+
45
+ 2. Create or update the working plan.
46
+ - Use `update_plan` for multi-step work.
47
+ - Include at least one verification/audit step.
48
+ - If an existing `xflow:plan` artifact is current, reuse it instead of replanning.
49
+
50
+ 3. Execute the next reversible step.
51
+ - Inspect before editing.
52
+ - Make scoped changes.
53
+ - Preserve unrelated user changes.
54
+ - Give concise progress updates during longer work.
55
+
56
+ 4. Verify with fresh evidence.
57
+ - Run the most relevant tests, build, typecheck, lint, UI/browser checks, or command-level proof.
58
+ - Read the output and confirm it proves the acceptance criteria.
59
+ - Passing a low-level command is not enough if the user-facing path remains untested.
60
+
61
+ 5. Self-heal on failure.
62
+ - Convert every failing test, error, review comment, or user correction into a concrete repair item.
63
+ - Update the plan, fix, and rerun the relevant verification.
64
+ - If the same failure recurs three times without new information, stop and report the blocker with evidence.
65
+
66
+ 6. Completion audit.
67
+ - Confirm the result still aligns with `.xflow/GOAL.md` when that file exists.
68
+ - If the result intentionally diverges from the goal, name the divergence and
69
+ require explicit user direction or an updated goal before calling the work
70
+ complete.
71
+ - Compare the original request and all later user updates against delivered files, behavior, and verification output.
72
+ - Confirm no plan item remains pending or in progress.
73
+ - Confirm no known failing check is being ignored.
74
+ - Only then report completion.
75
+
76
+ ## Autopilot Loop
77
+
78
+ Autopilot is the stricter full-delivery loop:
79
+
80
+ ```text
81
+ plan -> ralph -> review -> repair-or-complete
82
+ ```
83
+
84
+ 1. `plan`: Produce or update an implementation plan with acceptance criteria and verification commands.
85
+ 2. `ralph`: Execute the plan through the Ralph loop above.
86
+ 3. `review`: Review the changed work for bugs, regressions, missing tests, scope drift, and incomplete requirements.
87
+ 4. If review is clean, finish with evidence.
88
+ 5. If review is not clean, convert findings into planning input and return to `plan`.
89
+
90
+ Do not skip from vague input directly to implementation in autopilot mode. Do not patch review findings ad hoc without updating the plan.
91
+
92
+ ## Feedback Handling
93
+
94
+ Treat feedback as first-class input:
95
+
96
+ | Feedback source | Required action |
97
+ |---|---|
98
+ | Test failure | Extract failing assertion/error and repair the smallest cause |
99
+ | Build/type/lint failure | Fix the diagnostic, rerun the failed command |
100
+ | Code review finding | Add a repair item with severity and affected file |
101
+ | User says "not right" | Ask for specifics only if the issue cannot be inferred from artifacts |
102
+ | User changes direction | Preserve non-conflicting completed work; update only the active branch |
103
+
104
+ ## Stop Rules
105
+
106
+ Continue automatically through safe, reversible work. Stop and ask or report when:
107
+
108
+ - The next step is destructive, credentialed, external-production, financial, privacy-sensitive, or materially preference-dependent.
109
+ - Required credentials, services, or permissions are missing.
110
+ - The same blocker recurs three times.
111
+ - Verification cannot be run in this environment.
112
+ - The user says stop, cancel, pause, or asks only for status.
113
+
114
+ ## Final Response
115
+
116
+ Finish with:
117
+
118
+ - What changed.
119
+ - Which verification commands or checks passed.
120
+ - Any checks that could not be run and the residual risk.
121
+ - The most important remaining user decision, only if one exists.
122
+
123
+ Never say the task is complete merely because the code "looks good" or because an implementation pass ended. Completion requires evidence.
@@ -0,0 +1,44 @@
1
+ #!/bin/sh
2
+ # Check that the installed xflow skill matches this repo's xflow skill.
3
+ # The installed skill may keep .skillhub-source as source metadata.
4
+ # Ignore generated Python cache artifacts in both trees.
5
+
6
+ set -eu
7
+
8
+ SCRIPT_PATH=$0
9
+ case "$SCRIPT_PATH" in
10
+ /*) ;;
11
+ *) SCRIPT_PATH=$(pwd)/$SCRIPT_PATH ;;
12
+ esac
13
+
14
+ SCRIPT_DIR=$(cd "$(dirname "$SCRIPT_PATH")" && pwd)
15
+ PROJECT_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
16
+
17
+ SOURCE_DIR=${XFLOW_SKILL_SOURCE_DIR:-$PROJECT_ROOT/xflow}
18
+ TARGET_DIR=${XFLOW_INSTALLED_SKILL_DIR:-$HOME/.codex/skills/xflow}
19
+
20
+ if [ ! -d "$SOURCE_DIR" ]; then
21
+ printf 'check_installed_xflow_skill_sync.sh: missing source xflow dir: %s\n' "$SOURCE_DIR" >&2
22
+ exit 1
23
+ fi
24
+
25
+ if [ ! -d "$TARGET_DIR" ]; then
26
+ printf 'check_installed_xflow_skill_sync.sh: missing installed xflow dir: %s\n' "$TARGET_DIR" >&2
27
+ printf 'Run npm run skill:sync first, or set XFLOW_INSTALLED_SKILL_DIR.\n' >&2
28
+ exit 1
29
+ fi
30
+
31
+ diff_file=$(mktemp)
32
+ filtered_file=$(mktemp)
33
+ trap 'rm -f "$diff_file" "$filtered_file"' EXIT
34
+
35
+ diff -qr "$SOURCE_DIR" "$TARGET_DIR" >"$diff_file" 2>&1 || true
36
+ grep -v -E '(\.skillhub-source|__pycache__|\.pyc$)' "$diff_file" >"$filtered_file" || true
37
+
38
+ if [ -s "$filtered_file" ]; then
39
+ printf 'check_installed_xflow_skill_sync.sh: unexpected installed xflow skill drift:\n' >&2
40
+ cat "$filtered_file" >&2
41
+ exit 1
42
+ fi
43
+
44
+ printf 'verified xflow skill diff target: %s\n' "$TARGET_DIR"