@surlymochan/evalid 0.0.0-stage → 0.4.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.
- package/CHANGELOG.md +94 -0
- package/LICENSE +21 -0
- package/README.md +315 -2
- package/RELEASE_NOTES.md +124 -0
- package/atoms/registry.json +758 -0
- package/bin/xflow.js +7261 -0
- package/docs/adoption/README.md +128 -0
- package/docs/adoption/openflow-release-hardening.md +61 -0
- package/docs/benchmark-openflow-vs-gstack-superpowers-openspec-2026-05-25.md +81 -0
- package/docs/compatibility.md +52 -0
- package/docs/competitive-benchmark.md +108 -0
- package/docs/corps-operator-guide.md +75 -0
- package/docs/demo-proof.md +126 -0
- package/docs/examples-gallery.md +115 -0
- package/docs/fixtures/tracker-item.json +7 -0
- package/docs/goal-vs-codex.md +129 -0
- package/docs/gstack-comparison.md +88 -0
- package/docs/install-upgrade.md +133 -0
- package/docs/integrations.md +49 -0
- package/docs/launch-demo.md +123 -0
- package/docs/launch-dossier.md +180 -0
- package/docs/methodology.md +149 -0
- package/docs/npm-publish-handoff.md +96 -0
- package/docs/openspec-migration.md +106 -0
- package/docs/public-benchmark.md +68 -0
- package/docs/public-release.md +145 -0
- package/docs/quality-assessment.md +139 -0
- package/docs/quickstart.md +156 -0
- package/docs/reviewer-guide.md +43 -0
- package/docs/spec-kit-benchmark.md +73 -0
- package/docs/super-assistant-comparison.md +106 -0
- package/docs/superpowers-comparison.md +82 -0
- package/docs/team-adoption.md +123 -0
- package/docs/tooling-matrix.md +91 -0
- package/docs/walkthrough.md +76 -0
- package/docs/workflow-manual.md +1130 -0
- package/package.json +83 -4
- package/prompts/pencil-draft.md +46 -0
- package/prompts/pencil-refine.md +46 -0
- package/prompts/phase-artifact.md +24 -0
- package/prompts/phase-execution.md +32 -0
- package/schemas/competitor-reconstruction-contract.schema.json +91 -0
- package/schemas/llm-design-review-aggregate.schema.json +83 -0
- package/schemas/visual-benchmark.schema.json +978 -0
- package/schemas/workflow.schema.json +100 -0
- package/scripts/run-tests.mjs +23 -0
- package/scripts/takein_digest.py +139 -0
- package/specs/README.md +118 -0
- package/specs/architecture.md +36 -0
- package/specs/product.md +33 -0
- package/specs/workflow.md +349 -0
- package/src/agent_team/atoms/codex-app-bridge.js +8 -0
- package/src/agent_team/atoms/design/competitor-reconstruction-review.js +44 -0
- package/src/agent_team/atoms/design/contract-freeze.js +8 -0
- package/src/agent_team/atoms/design/generation-contract.js +442 -0
- package/src/agent_team/atoms/design/reconstruction-pack.js +246 -0
- package/src/agent_team/atoms/design/reference-surface-lock.js +49 -0
- package/src/agent_team/atoms/patch-challenge.js +19 -0
- package/src/agent_team/atoms/pencil/accept.js +9 -0
- package/src/agent_team/atoms/pencil/draft.js +365 -0
- package/src/agent_team/atoms/pencil/refine.js +229 -0
- package/src/agent_team/atoms/phase-run-dispatch.js +8 -0
- package/src/agent_team/atoms/team-run.js +380 -0
- package/src/agent_team/atoms/visual/aesthetic-review.js +231 -0
- package/src/agent_team/atoms/visual/benchmark-repair-plan.js +291 -0
- package/src/agent_team/atoms/visual/benchmark.js +1652 -0
- package/src/agent_team/atoms/visual/design-selection.js +23 -0
- package/src/agent_team/atoms/visual/direction-synthesis.js +124 -0
- package/src/agent_team/atoms/visual/dom-rect-export.js +129 -0
- package/src/agent_team/atoms/visual/image-diff.js +186 -0
- package/src/agent_team/atoms/visual/layout-competition.js +8 -0
- package/src/agent_team/atoms/visual/page-evidence-capture.js +806 -0
- package/src/agent_team/atoms/visual/playwright-runtime.js +8 -0
- package/src/agent_team/atoms/visual/review-aggregate.js +87 -0
- package/src/agent_team/atoms/visual/review.js +77 -0
- package/src/agent_team/atoms/visual/visual-report.js +197 -0
- package/src/agent_team/atoms/visual/visual-token-export.js +417 -0
- package/src/agent_team/common.js +311 -0
- package/src/core/atoms/agent-invoke.js +684 -0
- package/src/core/atoms/event/ingest.js +30 -0
- package/src/core/atoms/experience/digest.js +55 -0
- package/src/core/atoms/gate/artifact-verify.js +83 -0
- package/src/core/atoms/gate/competitor-contract-validate.js +119 -0
- package/src/core/atoms/gate/evidence-collect.js +64 -0
- package/src/core/atoms/gate/payload-from-phase-run.js +78 -0
- package/src/core/atoms/gate/visual-benchmark-validate.js +183 -0
- package/src/core/atoms/gate/workbench.js +79 -0
- package/src/core/atoms/intake/classify.js +15 -0
- package/src/core/atoms/intake/design-skill-select.js +16 -0
- package/src/core/atoms/intake/preview-or-refine.js +19 -0
- package/src/core/atoms/intake/recommend.js +20 -0
- package/src/core/atoms/intake/research-same-category.js +19 -0
- package/src/core/atoms/intervention/create.js +30 -0
- package/src/core/atoms/memory/manage.js +27 -0
- package/src/core/atoms/memory/mount.js +13 -0
- package/src/core/atoms/mission/create.js +19 -0
- package/src/core/atoms/mission/list.js +14 -0
- package/src/core/atoms/mission/show.js +26 -0
- package/src/core/atoms/notify/checkpoint.js +46 -0
- package/src/core/atoms/phase-run/complete.js +42 -0
- package/src/core/atoms/phase-run/fail.js +40 -0
- package/src/core/atoms/phase-run/persist.js +39 -0
- package/src/core/atoms/phase-run/start.js +30 -0
- package/src/core/atoms/plugin/run.js +76 -0
- package/src/core/atoms/policy/gate.js +90 -0
- package/src/core/atoms/policy/resolve.js +11 -0
- package/src/core/atoms/storage/sync.js +57 -0
- package/src/core/atoms/workflow/advance.js +25 -0
- package/src/core/atoms/workflow/guard.js +127 -0
- package/src/core/atoms/workflow/state-load.js +37 -0
- package/src/core/bindings.js +37 -0
- package/src/core/change-artifacts.js +134 -0
- package/src/core/competitive-score.js +265 -0
- package/src/core/concurrency-policy.js +35 -0
- package/src/core/execution-log.js +108 -0
- package/src/core/intake-recommendation.js +134 -0
- package/src/core/memory-state.js +62 -0
- package/src/core/mission-state.js +780 -0
- package/src/core/pending-gates.js +66 -0
- package/src/core/phase-cost.js +116 -0
- package/src/core/phase-run-read.js +27 -0
- package/src/core/policy-overlay.js +180 -0
- package/src/core/state-store.js +201 -0
- package/src/core/workflow-executor.js +1014 -0
- package/src/core/workflow-integrity.js +111 -0
- package/src/core/workflow-loader.js +163 -0
- package/src/core/workflow-rerun.js +84 -0
- package/src/core/workflow-state.js +119 -0
- package/src/server.js +1307 -0
- package/workflows/corps.yaml +481 -0
- package/workflows/yolo.yaml +241 -0
- package/xflow/README.md +76 -0
- package/xflow/aha/SKILL.md +82 -0
- package/xflow/aha/scripts/append_aha.py +50 -0
- package/xflow/archive/scripts/archive_change.py +338 -0
- package/xflow/archive/templates/merge-design.md +54 -0
- package/xflow/archive/templates/merge-workflow.md +26 -0
- package/xflow/atoms/__init__.py +6 -0
- package/xflow/atoms/a1_issue_create_or_patch.py +70 -0
- package/xflow/atoms/a2_branch_create_and_link.py +42 -0
- package/xflow/atoms/a3_repo_doctor.py +11 -0
- package/xflow/atoms/a4_project_set_status.py +11 -0
- package/xflow/atoms/a5_archive_commit_push_close.py +11 -0
- package/xflow/atoms/a6_pr_create.py +72 -0
- package/xflow/atoms/b1_change_scaffold.py +11 -0
- package/xflow/atoms/b2_status_read.py +16 -0
- package/xflow/atoms/b2b_status_write.py +20 -0
- package/xflow/atoms/b3_status_transition.py +27 -0
- package/xflow/atoms/b4_handoff_scaffold_or_refresh.py +11 -0
- package/xflow/atoms/b5_aha_append.py +11 -0
- package/xflow/atoms/b6_takein_digest.py +11 -0
- package/xflow/atoms/b6b_lookback_digest.py +11 -0
- package/xflow/atoms/b7_change_mission_bind.py +43 -0
- package/xflow/atoms/c1_mem.py +54 -0
- package/xflow/atoms/e6_gate_local_precheck.py +23 -0
- package/xflow/atoms/h1_design_lite_gate.py +74 -0
- package/xflow/atoms/i6a_tdd_run.py +107 -0
- package/xflow/atoms/i6b_tdd_proof_validate.py +184 -0
- package/xflow/atoms/i6c_tdd_quality_review.py +372 -0
- package/xflow/atoms/j1_tests_run.py +129 -0
- package/xflow/atoms/j4a_spec_consistency_structural.py +105 -0
- package/xflow/atoms/j4b_spec_delta_review.py +316 -0
- package/xflow/atoms/j4c_openspec_migration_map.py +121 -0
- package/xflow/atoms/j4d_spec_clarify_gate.py +373 -0
- package/xflow/atoms/j6_cr_review.py +443 -0
- package/xflow/atoms/k1_artifacts_complete_check.py +39 -0
- package/xflow/atoms/k2_merge_snippets_apply.py +11 -0
- package/xflow/atoms/k3_handoff_refresh.py +14 -0
- package/xflow/atoms/k4_mem_lesson_persist.py +65 -0
- package/xflow/atoms/k5_archive_publish.py +21 -0
- package/xflow/atoms/k6_aha_merge.py +42 -0
- package/xflow/atoms/tdd_lib.py +956 -0
- package/xflow/corps/SKILL.md +109 -0
- package/xflow/exec/scripts/set_issue_in_progress.py +159 -0
- package/xflow/goal/SKILL.md +150 -0
- package/xflow/handoff/SKILL.md +95 -0
- package/xflow/handoff/scripts/scaffold_handoff.py +304 -0
- package/xflow/lookback/SKILL.md +66 -0
- package/xflow/lookback/scripts/lookback_digest.py +46 -0
- package/xflow/openissue/scripts/open_issue_flow.py +438 -0
- package/xflow/plan/SKILL.md +47 -0
- package/xflow/ralph/SKILL.md +123 -0
- package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
- package/xflow/scripts/common.py +483 -0
- package/xflow/scripts/doctor.py +278 -0
- package/xflow/scripts/sync_installed_xflow_skill.sh +134 -0
- package/xflow/takein/SKILL.md +81 -0
- package/xflow/takein/scripts/takein_digest.py +88 -0
- package/xflow/yolo/SKILL.md +75 -0
- package/xflow/yolo/scripts/scaffold_specs_change.py +673 -0
- package/xflow/yolo/scripts/yolo_gate.py +312 -0
- 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,47 @@
|
|
|
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
|
+
## Lite-track merged pass
|
|
34
|
+
|
|
35
|
+
When the change will run the lite (yolo) track, do the red-proof authoring
|
|
36
|
+
in the same pass as the plan: write the failing test and fix the red test
|
|
37
|
+
command in the plan itself. The yolo workflow seals `tdd-red` immediately
|
|
38
|
+
after `plan`, so a separate test-authoring pass would only re-read the
|
|
39
|
+
same files. This does not apply to the corps track, which keeps planning
|
|
40
|
+
and proof authoring apart.
|
|
41
|
+
|
|
42
|
+
## Boundary
|
|
43
|
+
|
|
44
|
+
- This skill does not open issues.
|
|
45
|
+
- This skill does not create branches.
|
|
46
|
+
- This skill does not commit or archive.
|
|
47
|
+
- 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"
|