@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.
- package/CHANGELOG.md +74 -0
- package/LICENSE +21 -0
- package/README.md +315 -2
- package/RELEASE_NOTES.md +102 -0
- package/atoms/registry.json +758 -0
- package/bin/xflow.js +7208 -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 +83 -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 +1125 -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 +998 -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 +479 -0
- package/workflows/yolo.yaml +233 -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 +104 -0
- package/xflow/atoms/i6b_tdd_proof_validate.py +165 -0
- package/xflow/atoms/i6c_tdd_quality_review.py +357 -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 +747 -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 +38 -0
- package/xflow/ralph/SKILL.md +123 -0
- package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
- package/xflow/scripts/common.py +480 -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 +66 -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,278 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
sys.path.append(str(Path(__file__).resolve().parent))
|
|
10
|
+
|
|
11
|
+
from common import ( # noqa: E402
|
|
12
|
+
find_git_root,
|
|
13
|
+
get_current_branch,
|
|
14
|
+
get_remote_url,
|
|
15
|
+
gh_json,
|
|
16
|
+
load_status,
|
|
17
|
+
remote_branch_exists,
|
|
18
|
+
resolve_project_context,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def extract_repo_from_remote_url(remote_url: str) -> str | None:
|
|
23
|
+
import re
|
|
24
|
+
|
|
25
|
+
match = re.search(r"github\.com[:/](?P<repo>[^/]+/[^/.]+?)(?:\.git)?$", remote_url.strip(), flags=re.IGNORECASE)
|
|
26
|
+
if not match:
|
|
27
|
+
return None
|
|
28
|
+
return str(match.group("repo"))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def check(name: str, ok: bool, detail: str, *, value: Any | None = None, severity: str = "error") -> dict[str, Any]:
|
|
32
|
+
payload: dict[str, Any] = {
|
|
33
|
+
"name": name,
|
|
34
|
+
"ok": ok,
|
|
35
|
+
"detail": detail,
|
|
36
|
+
"severity": severity,
|
|
37
|
+
}
|
|
38
|
+
if value is not None:
|
|
39
|
+
payload["value"] = value
|
|
40
|
+
return payload
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_args() -> argparse.Namespace:
|
|
44
|
+
parser = argparse.ArgumentParser(description="Validate Superflow repo / remote / issue / branch / project / status.json consistency.")
|
|
45
|
+
parser.add_argument("--project-root", default=".", help="Project root containing specs/changes.")
|
|
46
|
+
parser.add_argument("--change-id", required=True, help="Superflow change id.")
|
|
47
|
+
parser.add_argument("--repo", help="Override target repo from status.json.")
|
|
48
|
+
parser.add_argument("--issue-number", type=int, help="Override issue number from status.json.")
|
|
49
|
+
parser.add_argument("--branch-name", help="Override linked branch from status.json.")
|
|
50
|
+
parser.add_argument("--checkout-path", help="Override checkout path from status.json.")
|
|
51
|
+
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON output.")
|
|
52
|
+
return parser.parse_args()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def issue_branch_comment_present(repo: str, issue_number: int, branch_name: str) -> bool:
|
|
56
|
+
marker = "<!-- xflow:branch-link -->"
|
|
57
|
+
try:
|
|
58
|
+
comments = gh_json(["api", f"repos/{repo}/issues/{issue_number}/comments"])
|
|
59
|
+
except subprocess.CalledProcessError:
|
|
60
|
+
return False
|
|
61
|
+
for comment in comments:
|
|
62
|
+
body = comment.get("body") or ""
|
|
63
|
+
if marker in body and f"`{branch_name}`" in body:
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_doctor_checks(
|
|
69
|
+
*,
|
|
70
|
+
project_root: str | Path,
|
|
71
|
+
change_id: str,
|
|
72
|
+
repo: str | None = None,
|
|
73
|
+
issue_number: int | None = None,
|
|
74
|
+
branch_name: str | None = None,
|
|
75
|
+
checkout_path_override: str | Path | None = None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
status_path, status = load_status(project_root, change_id)
|
|
78
|
+
|
|
79
|
+
repo = repo or status.get("issue_repo") or status.get("target_repo")
|
|
80
|
+
issue_number = issue_number or status.get("issue_number")
|
|
81
|
+
branch_name = branch_name or status.get("branch_name")
|
|
82
|
+
checkout_path_value = str(checkout_path_override) if checkout_path_override else status.get("checkout_path")
|
|
83
|
+
code_repo = status.get("code_repo")
|
|
84
|
+
checkout_path = None
|
|
85
|
+
checks: list[dict[str, Any]] = []
|
|
86
|
+
|
|
87
|
+
checks.append(
|
|
88
|
+
check(
|
|
89
|
+
"status-file",
|
|
90
|
+
status_path.exists(),
|
|
91
|
+
f"Loaded status file {status_path}" if status_path.exists() else f"Missing status file {status_path}",
|
|
92
|
+
value=str(status_path),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
checks.append(check("status-issue-repo", bool(repo), "issue_repo is present" if repo else "issue_repo missing from status.json"))
|
|
96
|
+
checks.append(check("status-issue-number", bool(issue_number), "issue_number is present" if issue_number else "issue_number missing from status.json"))
|
|
97
|
+
checks.append(check("status-branch-name", bool(branch_name), "branch_name is present" if branch_name else "branch_name missing from status.json"))
|
|
98
|
+
checks.append(check("status-code-repo", bool(code_repo), "code_repo is present" if code_repo else "code_repo missing from status.json", value=code_repo, severity="warn"))
|
|
99
|
+
checks.append(
|
|
100
|
+
check(
|
|
101
|
+
"status-checkout-path",
|
|
102
|
+
bool(checkout_path_value),
|
|
103
|
+
"checkout_path is present" if checkout_path_value else "checkout_path missing from status.json",
|
|
104
|
+
value=checkout_path_value,
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if checkout_path_value:
|
|
109
|
+
try:
|
|
110
|
+
checkout_path = Path(checkout_path_value).expanduser().resolve()
|
|
111
|
+
checks.append(check("checkout-resolve", True, f"Resolved checkout {checkout_path}", value=str(checkout_path)))
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
checks.append(check("checkout-resolve", False, f"Unable to resolve checkout path: {exc}", severity="error"))
|
|
114
|
+
else:
|
|
115
|
+
try:
|
|
116
|
+
checkout_path = find_git_root(project_root)
|
|
117
|
+
checks.append(check("checkout-resolve", True, f"Resolved checkout {checkout_path} from project root", value=str(checkout_path)))
|
|
118
|
+
except subprocess.CalledProcessError as exc:
|
|
119
|
+
checks.append(check("checkout-resolve", False, f"Unable to resolve checkout from project root: {exc}", severity="error"))
|
|
120
|
+
|
|
121
|
+
checkout_remote_url = None
|
|
122
|
+
checkout_repo = None
|
|
123
|
+
if checkout_path:
|
|
124
|
+
try:
|
|
125
|
+
checkout_remote_url = get_remote_url(checkout_path)
|
|
126
|
+
checkout_repo = extract_repo_from_remote_url(checkout_remote_url)
|
|
127
|
+
checks.append(check("checkout-remote", True, "Resolved checkout remote", value={"remote_url": checkout_remote_url, "checkout_repo": checkout_repo}))
|
|
128
|
+
except subprocess.CalledProcessError as exc:
|
|
129
|
+
checks.append(check("checkout-remote", False, f"Unable to resolve checkout remote: {exc}", severity="error"))
|
|
130
|
+
|
|
131
|
+
if code_repo and checkout_repo:
|
|
132
|
+
checks.append(
|
|
133
|
+
check(
|
|
134
|
+
"checkout-code-repo-match",
|
|
135
|
+
code_repo == checkout_repo,
|
|
136
|
+
"checkout remote matches recorded code_repo" if code_repo == checkout_repo else "checkout remote differs from recorded code_repo",
|
|
137
|
+
value={"recorded_code_repo": code_repo, "checkout_repo": checkout_repo},
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
current_branch = None
|
|
142
|
+
if checkout_path:
|
|
143
|
+
try:
|
|
144
|
+
current_branch = get_current_branch(checkout_path)
|
|
145
|
+
checks.append(check("current-branch", True, "Current branch resolved", value=current_branch))
|
|
146
|
+
except subprocess.CalledProcessError as exc:
|
|
147
|
+
checks.append(check("current-branch", False, f"Unable to resolve current branch: {exc}", severity="error"))
|
|
148
|
+
|
|
149
|
+
if branch_name and checkout_path:
|
|
150
|
+
linked_branch_exists = bool(current_branch == branch_name or remote_branch_exists(checkout_path, branch_name))
|
|
151
|
+
checks.append(
|
|
152
|
+
check(
|
|
153
|
+
"linked-branch-local-or-remote",
|
|
154
|
+
linked_branch_exists,
|
|
155
|
+
f"Linked branch {branch_name} exists locally or on origin" if linked_branch_exists else f"Linked branch {branch_name} not found locally or on origin",
|
|
156
|
+
value=branch_name,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if current_branch is not None:
|
|
160
|
+
checks.append(
|
|
161
|
+
check(
|
|
162
|
+
"linked-branch-current-match",
|
|
163
|
+
current_branch == branch_name,
|
|
164
|
+
f"Current branch matches linked branch {branch_name}" if current_branch == branch_name else f"Current branch {current_branch} differs from linked branch {branch_name}",
|
|
165
|
+
value={"current_branch": current_branch, "linked_branch": branch_name},
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
checks.append(
|
|
169
|
+
check(
|
|
170
|
+
"branch-worktree-ready",
|
|
171
|
+
linked_branch_exists and current_branch == branch_name,
|
|
172
|
+
"Linked branch is available and the current checkout matches it"
|
|
173
|
+
if (linked_branch_exists and current_branch == branch_name)
|
|
174
|
+
else f"Linked branch/worktree is not ready: current={current_branch}, linked={branch_name}",
|
|
175
|
+
value={
|
|
176
|
+
"current_branch": current_branch,
|
|
177
|
+
"linked_branch": branch_name,
|
|
178
|
+
"checkout_path": str(checkout_path),
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
issue_data = None
|
|
184
|
+
if repo and issue_number:
|
|
185
|
+
try:
|
|
186
|
+
issue_data = gh_json(
|
|
187
|
+
[
|
|
188
|
+
"issue",
|
|
189
|
+
"view",
|
|
190
|
+
str(issue_number),
|
|
191
|
+
"--repo",
|
|
192
|
+
repo,
|
|
193
|
+
"--json",
|
|
194
|
+
"number,title,url,state,projectItems",
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
checks.append(check("issue-exists", True, f"Issue {repo}#{issue_number} exists", value={"url": issue_data.get("url"), "state": issue_data.get("state")}))
|
|
198
|
+
except subprocess.CalledProcessError as exc:
|
|
199
|
+
checks.append(check("issue-exists", False, f"Unable to resolve issue {repo}#{issue_number}: {exc}", severity="error"))
|
|
200
|
+
|
|
201
|
+
if repo and issue_number and branch_name:
|
|
202
|
+
has_branch_comment = issue_branch_comment_present(repo, int(issue_number), branch_name)
|
|
203
|
+
checks.append(
|
|
204
|
+
check(
|
|
205
|
+
"issue-branch-comment-link",
|
|
206
|
+
has_branch_comment,
|
|
207
|
+
"Found Superflow branch-link comment on issue" if has_branch_comment else "Missing Superflow branch-link comment on issue",
|
|
208
|
+
value=branch_name,
|
|
209
|
+
severity="warn",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
project_context = None
|
|
214
|
+
if repo and issue_number:
|
|
215
|
+
project_context = resolve_project_context(repo, int(issue_number))
|
|
216
|
+
checks.append(
|
|
217
|
+
check(
|
|
218
|
+
"project-context",
|
|
219
|
+
project_context is not None,
|
|
220
|
+
f"Resolved project context {project_context['owner']}#{project_context['number']}" if project_context else "No unique project context discovered from issue",
|
|
221
|
+
value=project_context,
|
|
222
|
+
severity="warn",
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
recorded_project = status.get("project_status") or status.get("project_done") or status.get("project_context")
|
|
227
|
+
if recorded_project and project_context:
|
|
228
|
+
same_owner = recorded_project.get("owner") == project_context.get("owner")
|
|
229
|
+
same_number = int(recorded_project.get("number")) == int(project_context.get("number"))
|
|
230
|
+
checks.append(
|
|
231
|
+
check(
|
|
232
|
+
"project-record-match",
|
|
233
|
+
same_owner and same_number,
|
|
234
|
+
"Recorded project context matches discovered project context" if (same_owner and same_number) else "Recorded project context differs from discovered project context",
|
|
235
|
+
value={"recorded": recorded_project, "discovered": project_context},
|
|
236
|
+
severity="warn",
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
ok = all(item["ok"] or item["severity"] == "warn" for item in checks)
|
|
241
|
+
hard_failures = [item for item in checks if (not item["ok"] and item["severity"] != "warn")]
|
|
242
|
+
warnings = [item for item in checks if (not item["ok"] and item["severity"] == "warn")]
|
|
243
|
+
|
|
244
|
+
result = {
|
|
245
|
+
"change_id": change_id,
|
|
246
|
+
"ok": ok,
|
|
247
|
+
"hard_failures": len(hard_failures),
|
|
248
|
+
"warnings": len(warnings),
|
|
249
|
+
"checks": checks,
|
|
250
|
+
}
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def main() -> int:
|
|
255
|
+
args = parse_args()
|
|
256
|
+
result = run_doctor_checks(
|
|
257
|
+
project_root=args.project_root,
|
|
258
|
+
change_id=args.change_id,
|
|
259
|
+
repo=args.repo,
|
|
260
|
+
issue_number=args.issue_number,
|
|
261
|
+
branch_name=args.branch_name,
|
|
262
|
+
checkout_path_override=args.checkout_path,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if args.json:
|
|
266
|
+
print(json.dumps(result, indent=2))
|
|
267
|
+
else:
|
|
268
|
+
print(f"Superflow doctor: {args.change_id}")
|
|
269
|
+
print(f"Overall: {'OK' if result['ok'] else 'FAIL'}")
|
|
270
|
+
for item in result["checks"]:
|
|
271
|
+
icon = "PASS" if item["ok"] else ("WARN" if item["severity"] == "warn" else "FAIL")
|
|
272
|
+
print(f"- [{icon}] {item['name']}: {item['detail']}")
|
|
273
|
+
|
|
274
|
+
return 0 if result["ok"] else 1
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Sync the repo-owned xflow skill into installed skill dirs without pruning
|
|
3
|
+
# skills that are managed by companion repositories.
|
|
4
|
+
|
|
5
|
+
set -eu
|
|
6
|
+
|
|
7
|
+
SCRIPT_PATH=$0
|
|
8
|
+
case "$SCRIPT_PATH" in
|
|
9
|
+
/*) ;;
|
|
10
|
+
*) SCRIPT_PATH=$(pwd)/$SCRIPT_PATH ;;
|
|
11
|
+
esac
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR=$(cd "$(dirname "$SCRIPT_PATH")" && pwd)
|
|
14
|
+
PROJECT_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
|
|
15
|
+
|
|
16
|
+
config_value() {
|
|
17
|
+
key_path=$1
|
|
18
|
+
node -e '
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const path = require("path");
|
|
21
|
+
const root = process.argv[1];
|
|
22
|
+
const keyPath = process.argv[2].split(".");
|
|
23
|
+
const configPath = path.join(root, ".as-xflow", "config.json");
|
|
24
|
+
if (!fs.existsSync(configPath)) process.exit(0);
|
|
25
|
+
const expandHome = (value) => typeof value === "string" && value.startsWith("~/")
|
|
26
|
+
? path.join(process.env.HOME || "", value.slice(2))
|
|
27
|
+
: value;
|
|
28
|
+
let value = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
29
|
+
for (const key of keyPath) value = value && value[key];
|
|
30
|
+
if (Array.isArray(value)) console.log(value.map(expandHome).join("\n"));
|
|
31
|
+
else if (value) console.log(expandHome(value));
|
|
32
|
+
' "$PROJECT_ROOT" "$key_path"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
CONFIG_SYNC_SCRIPT=$(config_value skills.sync_script)
|
|
36
|
+
CONFIG_EXTRA_SOURCE_DIRS=$(config_value skills.extra_source_dirs)
|
|
37
|
+
CONFIG_TARGET_DIRS=$(config_value skills.installed_dir)
|
|
38
|
+
|
|
39
|
+
default_target_dirs() {
|
|
40
|
+
if [ -d "$HOME/.codex/skills" ]; then
|
|
41
|
+
printf '%s\n' "$HOME/.codex/skills"
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
printf '%s\n' "$HOME/.agents/skills"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SYNC_SCRIPT=${XFLOW_SKILL_SYNC_SCRIPT:-${SKILLHUB_SYNC_SCRIPT:-${CONFIG_SYNC_SCRIPT:-$HOME/Documents/workspace/pro/as-skillhub/skills/skills_sync.sh}}}
|
|
49
|
+
EXTRA_SOURCE_DIRS=${XFLOW_SKILL_SYNC_EXTRA_SOURCE_DIRS:-${SKILL_SYNC_EXTRA_SOURCE_DIRS:-${CONFIG_EXTRA_SOURCE_DIRS:-$HOME/Documents/workspace/pro/as-skillhub/skills}}}
|
|
50
|
+
TARGET_DIRS=${SKILL_SYNC_TARGET_DIRS:-${CONFIG_TARGET_DIRS:-$(default_target_dirs)}}
|
|
51
|
+
DEFAULT_XMEM_SOURCE_DIR=$(cd "$PROJECT_ROOT/../as-xmem" 2>/dev/null && pwd || true)
|
|
52
|
+
XMEM_SOURCE_DIR=${XFLOW_XMEM_SOURCE_DIR:-$DEFAULT_XMEM_SOURCE_DIR}
|
|
53
|
+
|
|
54
|
+
if [ ! -f "$SYNC_SCRIPT" ]; then
|
|
55
|
+
printf 'sync_installed_xflow_skill.sh: missing skills sync script: %s\n' "$SYNC_SCRIPT" >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ ! -d "$PROJECT_ROOT/xflow" ]; then
|
|
60
|
+
printf 'sync_installed_xflow_skill.sh: missing source xflow dir: %s\n' "$PROJECT_ROOT/xflow" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
extra_source_found=$(mktemp)
|
|
65
|
+
printf '%s\n' "$EXTRA_SOURCE_DIRS" | while IFS= read -r source_dir; do
|
|
66
|
+
[ -n "$source_dir" ] || continue
|
|
67
|
+
[ -d "$source_dir" ] || continue
|
|
68
|
+
printf 'yes\n' >"$extra_source_found"
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
if [ ! -s "$extra_source_found" ]; then
|
|
72
|
+
rm -f "$extra_source_found"
|
|
73
|
+
printf 'sync_installed_xflow_skill.sh: no extra source dir exists in:\n%s\n' "$EXTRA_SOURCE_DIRS" >&2
|
|
74
|
+
printf 'Set XFLOW_SKILL_SYNC_EXTRA_SOURCE_DIRS or SKILL_SYNC_EXTRA_SOURCE_DIRS to the as-skillhub skills dir.\n' >&2
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
rm -f "$extra_source_found"
|
|
78
|
+
|
|
79
|
+
SKILLHUB="$PROJECT_ROOT" \
|
|
80
|
+
SKILL_SYNC_EXTRA_SOURCE_DIRS="$EXTRA_SOURCE_DIRS" \
|
|
81
|
+
SKILL_SYNC_TARGET_DIRS="$TARGET_DIRS" \
|
|
82
|
+
sh "$SYNC_SCRIPT" xflow
|
|
83
|
+
|
|
84
|
+
install_xmem_skill_family() {
|
|
85
|
+
[ -n "$XMEM_SOURCE_DIR" ] || return 0
|
|
86
|
+
[ -d "$XMEM_SOURCE_DIR" ] || return 0
|
|
87
|
+
[ -f "$XMEM_SOURCE_DIR/skill.md" ] || return 0
|
|
88
|
+
|
|
89
|
+
printf '%s\n' "$TARGET_DIRS" | while IFS= read -r target_dir; do
|
|
90
|
+
[ -n "$target_dir" ] || continue
|
|
91
|
+
target_xmem=$target_dir/xmem
|
|
92
|
+
|
|
93
|
+
rm -rf "$target_xmem"
|
|
94
|
+
cp -RL "$XMEM_SOURCE_DIR" "$target_xmem"
|
|
95
|
+
rm -f "$target_xmem/.xmem-root-skill.tmp"
|
|
96
|
+
mv "$target_xmem/skill.md" "$target_xmem/.xmem-root-skill.tmp"
|
|
97
|
+
mv "$target_xmem/.xmem-root-skill.tmp" "$target_xmem/SKILL.md"
|
|
98
|
+
rm -f "$target_xmem/.skillhub-source"
|
|
99
|
+
printf '%s\n' "$XMEM_SOURCE_DIR" >"$target_xmem/.xflow-source"
|
|
100
|
+
|
|
101
|
+
find "$target_xmem" -type d -name '__pycache__' -prune -exec rm -rf {} +
|
|
102
|
+
find "$target_xmem" -type f -name '*.pyc' -delete
|
|
103
|
+
printf 'verified xmem skill sync target: %s\n' "$target_xmem"
|
|
104
|
+
done
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
install_xmem_skill_family
|
|
108
|
+
|
|
109
|
+
printf '%s\n' "$TARGET_DIRS" | while IFS= read -r target_dir; do
|
|
110
|
+
[ -n "$target_dir" ] || continue
|
|
111
|
+
target_xflow=$target_dir/xflow
|
|
112
|
+
|
|
113
|
+
if [ ! -d "$target_xflow" ]; then
|
|
114
|
+
printf 'sync_installed_xflow_skill.sh: target xflow dir missing after sync: %s\n' "$target_xflow" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
find "$target_xflow" -type d -name '__pycache__' -prune -exec rm -rf {} +
|
|
119
|
+
find "$target_xflow" -type f -name '*.pyc' -delete
|
|
120
|
+
|
|
121
|
+
diff_file=$(mktemp)
|
|
122
|
+
filtered_file=$(mktemp)
|
|
123
|
+
diff -qr "$PROJECT_ROOT/xflow" "$target_xflow" >"$diff_file" 2>&1 || true
|
|
124
|
+
grep -v -E 'Only in .*/xflow: \.skillhub-source$|__pycache__|\.pyc$' "$diff_file" >"$filtered_file" || true
|
|
125
|
+
|
|
126
|
+
if [ -s "$filtered_file" ]; then
|
|
127
|
+
cat "$filtered_file" >&2
|
|
128
|
+
rm -f "$diff_file" "$filtered_file"
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
rm -f "$diff_file" "$filtered_file"
|
|
133
|
+
printf 'verified xflow skill sync target: %s\n' "$target_xflow"
|
|
134
|
+
done
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xflow:takein
|
|
3
|
+
description: 通过读取项目根目录的规范 `HANDOFF.md`,提取当前目标、进展、阻塞、经验和下一步动作,并在执行前完成一次轻量级仓库状态摄入,从而在新线程中接手工作。适用于用户要求在新线程继续、接管项目、根据 handoff 衔接、恢复上次会话,或从 `HANDOFF.md` 启动上下文时。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Superflow Takein
|
|
7
|
+
|
|
8
|
+
This skill is for new-thread takeover.
|
|
9
|
+
|
|
10
|
+
Your primary source of truth is:
|
|
11
|
+
|
|
12
|
+
- project root `.xflow/GOAL.md` (if present)
|
|
13
|
+
- project root `HANDOFF.md`
|
|
14
|
+
- project root `AHA.md`
|
|
15
|
+
- project root `DESIGN.md` (if frontend project)
|
|
16
|
+
|
|
17
|
+
Do not start by reading a large random slice of the repository.
|
|
18
|
+
Start from `HANDOFF.md`, then validate only the minimum additional context needed to begin the task safely.
|
|
19
|
+
|
|
20
|
+
## Goal
|
|
21
|
+
|
|
22
|
+
Build a correct working context for the new thread fast enough to act, without redoing full-project discovery.
|
|
23
|
+
|
|
24
|
+
## Required Intake Output
|
|
25
|
+
|
|
26
|
+
Before substantial implementation work, produce an intake summary covering:
|
|
27
|
+
|
|
28
|
+
1. project background
|
|
29
|
+
2. architecture and invariants
|
|
30
|
+
3. current objective
|
|
31
|
+
4. verified progress
|
|
32
|
+
5. blockers / risks / open problems
|
|
33
|
+
6. lessons learned / operational gotchas
|
|
34
|
+
7. recommended first action in this thread
|
|
35
|
+
|
|
36
|
+
If `HANDOFF.md` is missing or stale, say so explicitly and repair the situation instead of pretending the context is complete.
|
|
37
|
+
|
|
38
|
+
## Workflow
|
|
39
|
+
|
|
40
|
+
1. Read project root `.xflow/GOAL.md` if present.
|
|
41
|
+
2. Read project root `HANDOFF.md`.
|
|
42
|
+
3. Read project root `AHA.md` if present.
|
|
43
|
+
4. Read project root `DESIGN.md` if present (frontend projects).
|
|
44
|
+
5. Extract the current objective, verified state, blockers, recommendations, and any active constraints.
|
|
45
|
+
6. Run a lightweight repo-state check:
|
|
46
|
+
- `git status -sb`
|
|
47
|
+
- `npm run drift:scan` when the project exposes it; for evalid this is required before substantial implementation
|
|
48
|
+
- inspect directly relevant files only
|
|
49
|
+
7. Compare current repo reality against `.xflow/GOAL.md`, `HANDOFF.md`, `AHA.md`, and `DESIGN.md`.
|
|
50
|
+
8. Call out any mismatch between goal, handoff, aha log, design decisions, and repo state.
|
|
51
|
+
9. State the first concrete action you will take next.
|
|
52
|
+
10. Then proceed with execution.
|
|
53
|
+
|
|
54
|
+
## Scope Control
|
|
55
|
+
|
|
56
|
+
Keep intake tight.
|
|
57
|
+
|
|
58
|
+
- do not re-read the entire codebase unless the handoff is clearly insufficient
|
|
59
|
+
- do not expand into open-ended architecture exploration unless blocked
|
|
60
|
+
- do not overwrite `HANDOFF.md` during takein unless the user asks, or the file is clearly dangerously stale
|
|
61
|
+
|
|
62
|
+
## Guardrails
|
|
63
|
+
|
|
64
|
+
- treat `HANDOFF.md` as canonical until contradicted by repo evidence
|
|
65
|
+
- treat `AHA.md` as canonical for durable discussion insights until contradicted by repo evidence
|
|
66
|
+
- prefer verified repo truth over stale handoff claims
|
|
67
|
+
- distinguish verified facts from assumptions
|
|
68
|
+
- surface drift immediately if handoff and code disagree
|
|
69
|
+
- surface drift immediately if current design or implementation conflicts with a recorded aha
|
|
70
|
+
- always end intake with a recommended first action
|
|
71
|
+
|
|
72
|
+
## Script
|
|
73
|
+
|
|
74
|
+
The agent autonomously calls this helper to generate intake digest:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python3 ~/.codex/skills/xflow/takein/scripts/takein_digest.py --project-root .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Agent uses digest as starting point, then validates repo reality.
|
|
81
|
+
User invokes skill, agent handles execution.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
SECTION_HEADERS = [
|
|
7
|
+
"Current State",
|
|
8
|
+
"Context Pack",
|
|
9
|
+
"Project Background",
|
|
10
|
+
"Architecture",
|
|
11
|
+
"Current Objective",
|
|
12
|
+
"Progress",
|
|
13
|
+
"Open Problems",
|
|
14
|
+
"Lessons Learned",
|
|
15
|
+
"Recommendations",
|
|
16
|
+
"Key Files",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_args() -> argparse.Namespace:
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
description="Extract a compact digest from the canonical root HANDOFF.md and AHA.md.",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--project-root",
|
|
26
|
+
default=".",
|
|
27
|
+
help="Project root containing HANDOFF.md and optional AHA.md.",
|
|
28
|
+
)
|
|
29
|
+
return parser.parse_args()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def extract_sections(text: str) -> list[tuple[str, list[str]]]:
|
|
33
|
+
sections: list[tuple[str, list[str]]] = []
|
|
34
|
+
current_name = "Preamble"
|
|
35
|
+
current_lines: list[str] = []
|
|
36
|
+
|
|
37
|
+
for line in text.splitlines():
|
|
38
|
+
if line.startswith("## "):
|
|
39
|
+
sections.append((current_name, current_lines))
|
|
40
|
+
current_name = line[3:].strip()
|
|
41
|
+
current_lines = []
|
|
42
|
+
continue
|
|
43
|
+
current_lines.append(line)
|
|
44
|
+
|
|
45
|
+
sections.append((current_name, current_lines))
|
|
46
|
+
return sections
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def first_content_lines(lines: list[str], limit: int = 3) -> list[str]:
|
|
50
|
+
content = [line.strip() for line in lines if line.strip()]
|
|
51
|
+
return content[:limit]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main() -> int:
|
|
55
|
+
args = parse_args()
|
|
56
|
+
project_root = Path(args.project_root).expanduser().resolve()
|
|
57
|
+
handoff_path = project_root / "HANDOFF.md"
|
|
58
|
+
aha_path = project_root / "AHA.md"
|
|
59
|
+
if not handoff_path.exists():
|
|
60
|
+
raise SystemExit(f"Missing canonical handoff: {handoff_path}")
|
|
61
|
+
|
|
62
|
+
text = handoff_path.read_text(encoding="utf-8")
|
|
63
|
+
sections = dict(extract_sections(text))
|
|
64
|
+
|
|
65
|
+
print(f"HANDOFF: {handoff_path}")
|
|
66
|
+
for name in SECTION_HEADERS:
|
|
67
|
+
lines = sections.get(name)
|
|
68
|
+
if not lines:
|
|
69
|
+
continue
|
|
70
|
+
digest = first_content_lines(lines)
|
|
71
|
+
if not digest:
|
|
72
|
+
continue
|
|
73
|
+
print(f"\n[{name}]")
|
|
74
|
+
for line in digest:
|
|
75
|
+
print(line)
|
|
76
|
+
|
|
77
|
+
if aha_path.exists():
|
|
78
|
+
aha_lines = [line.strip() for line in aha_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
79
|
+
digest = aha_lines[:12]
|
|
80
|
+
if digest:
|
|
81
|
+
print(f"\n[AHA] {aha_path}")
|
|
82
|
+
for line in digest:
|
|
83
|
+
print(line)
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xflow:yolo
|
|
3
|
+
description: "轻量轨交付工作流:从 change-init 到 archive。适用于后端、文档、基础设施和小型 UI 改动。可复用 xflow:plan 产物,或在缺少 plan 时先补一轮通用规划。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Superflow Yolo — Track Execution Driver
|
|
7
|
+
|
|
8
|
+
This skill drives the lite workflow. It does not own a separate planning
|
|
9
|
+
track; planning is shared with `xflow:plan`.
|
|
10
|
+
|
|
11
|
+
## How to run
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
xflow workflow validate workflows/yolo.yaml
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then execute the workflow phases in order.
|
|
18
|
+
|
|
19
|
+
For a full run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
xflow workflow run workflows/yolo.yaml --title "<中文标题>" --change-type backend
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Plan reuse rule
|
|
26
|
+
|
|
27
|
+
- Read `.xflow/GOAL.md` before execution when present. State whether the current
|
|
28
|
+
change aligns with it, intentionally narrows it, or conflicts with it.
|
|
29
|
+
- Reuse `specs/changes/${CHANGE_ID}/plan.md` when it is current.
|
|
30
|
+
- If the plan is missing or still a scaffold placeholder, create it once before continuing.
|
|
31
|
+
- Planning stays track-neutral; do not decide yolo vs corps here.
|
|
32
|
+
|
|
33
|
+
## State and context
|
|
34
|
+
|
|
35
|
+
- Project root: `process.env.XFLOW_PROJECT_ROOT || cwd`
|
|
36
|
+
- Project goal: `.xflow/GOAL.md` when present; use it as direction context only
|
|
37
|
+
- Change root: `specs/changes/${CHANGE_ID}/`
|
|
38
|
+
- Status file: `specs/changes/${CHANGE_ID}/status.json`
|
|
39
|
+
|
|
40
|
+
## Phase playbook
|
|
41
|
+
|
|
42
|
+
1. `change-init`
|
|
43
|
+
2. `brainstorm`
|
|
44
|
+
3. `design-check` if the change is frontend/UI
|
|
45
|
+
4. `proposal-freeze`
|
|
46
|
+
5. `proposal-consistency-check`
|
|
47
|
+
6. `plan`
|
|
48
|
+
7. `openissue`
|
|
49
|
+
8. `set-in-progress`
|
|
50
|
+
9. `tdd`
|
|
51
|
+
10. `execute`
|
|
52
|
+
11. `verify-consistency`
|
|
53
|
+
12. `archive`
|
|
54
|
+
|
|
55
|
+
The workflow YAML is the canonical execution source for atom order, gates, and
|
|
56
|
+
artifact checks. This skill only explains the contract.
|
|
57
|
+
|
|
58
|
+
## Guardrails
|
|
59
|
+
|
|
60
|
+
- Keep the plan reusable and track-neutral
|
|
61
|
+
- Do not introduce a separate tasks artifact
|
|
62
|
+
- Do not re-plan if a current `plan.md` already exists
|
|
63
|
+
- Do not treat `.xflow/GOAL.md` as executable scope; it is alignment context
|
|
64
|
+
that must be reflected in the final verification summary
|
|
65
|
+
- Do not enter `set-in-progress` or `execute` unless doctor confirms the linked branch and checkout are ready
|
|
66
|
+
- Do not expose deleted top-level xflow utility skills as entry points
|