@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.
Files changed (192) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE +21 -0
  3. package/README.md +315 -2
  4. package/RELEASE_NOTES.md +124 -0
  5. package/atoms/registry.json +758 -0
  6. package/bin/xflow.js +7261 -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 +96 -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 +1130 -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 +1014 -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 +481 -0
  131. package/workflows/yolo.yaml +241 -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 +107 -0
  158. package/xflow/atoms/i6b_tdd_proof_validate.py +184 -0
  159. package/xflow/atoms/i6c_tdd_quality_review.py +372 -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 +956 -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 +47 -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 +483 -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 +75 -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,76 @@
1
+ # xflow
2
+
3
+ Project-facing skill family for `evalid`, an executable delivery workflow runtime for AI coding agents.
4
+
5
+ Start here:
6
+
7
+ ```bash
8
+ xflow guide
9
+ xflow assess
10
+ xflow demo clean
11
+ xflow demo launch
12
+ xflow launch audit
13
+ xflow adoption kit --name first-team --source first-tracker --track yolo
14
+ xflow adoption init --name first-team --source first-tracker --track yolo
15
+ xflow adoption validate --json
16
+ xflow package audit --check-registry --json
17
+ xflow init --project-root .
18
+ xflow goal set "Ship the next verified change" --project-root .
19
+ xflow goal audit --project-root . --json
20
+ xflow doctor --project-root .
21
+ xflow workflow validate yolo --project-root .
22
+ ```
23
+
24
+ Core entry points:
25
+
26
+ - `xflow:takein` - load `HANDOFF.md`, `AHA.md`, and local git state before acting.
27
+ - `xflow:goal` - set, inspect, or audit the project direction anchor in `.xflow/GOAL.md`; CLI users can run `xflow goal set` and `xflow goal audit`.
28
+ - `xflow:plan` - track-neutral planning only; produces `plan.md`.
29
+ - `xflow:ralph` - conversation-native Ralph/autopilot loop for verified completion and feedback repair.
30
+ - `xflow:yolo` - lite execution track for backend, docs, infra, and small UI changes.
31
+ - `xflow:corps` - heavy execution track for product/UI and multi-agent delivery.
32
+
33
+ Supporting entry points:
34
+
35
+ - `xflow:handoff` - refresh project handoff context.
36
+ - `xflow:lookback` - re-check durable project context before acting.
37
+ - `xflow:aha` - append durable insights to `AHA.md`.
38
+
39
+ Adjacent project:
40
+
41
+ - `xmem` - separate shared-memory skill family, now hosted in `as-xmem`.
42
+
43
+ Public docs:
44
+
45
+ - `README.md`
46
+ - `docs/quickstart.md`
47
+ - `docs/corps-operator-guide.md`
48
+ - `docs/install-upgrade.md`
49
+ - `docs/tooling-matrix.md`
50
+ - `docs/demo-proof.md`
51
+ - `docs/examples-gallery.md`
52
+ - `docs/public-benchmark.md`
53
+ - `docs/quality-assessment.md`
54
+ - `docs/launch-demo.md`
55
+ - `docs/launch-dossier.md`
56
+ - `docs/adoption/README.md`
57
+ - `docs/goal-vs-codex.md`
58
+ - `docs/walkthrough.md`
59
+ - `docs/openspec-migration.md`
60
+ - `docs/spec-kit-benchmark.md`
61
+ - `docs/integrations.md`
62
+ - `docs/public-release.md`
63
+ - `RELEASE_NOTES.md`
64
+ - `docs/fixtures/tracker-item.json`
65
+ - `docs/compatibility.md`
66
+ - `docs/methodology.md`
67
+ - `docs/reviewer-guide.md`
68
+ - `docs/team-adoption.md`
69
+ - `docs/competitive-benchmark.md`
70
+
71
+ Executable sources:
72
+
73
+ - `workflows/yolo.yaml`
74
+ - `workflows/corps.yaml`
75
+ - `atoms/registry.json`
76
+ - `specs/workflow.md`
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: xflow:aha
3
+ description: 将高价值讨论洞察、设计认知、流程经验和具持续影响的转折点沉淀到项目根目录的规范 `AHA.md` 中。适用于当前线程产生了会影响后续执行、评审、设计或流程决策的重要认识时。
4
+ ---
5
+
6
+ # Superflow Aha
7
+
8
+ Write durable insight into exactly one canonical file:
9
+
10
+ - project root `AHA.md`
11
+
12
+ `AHA.md` is not a handoff replacement.
13
+ It is the canonical log of high-signal realizations that would be costly to rediscover.
14
+
15
+ ## When to record
16
+
17
+ Record an aha when any of these become true:
18
+
19
+ - a root cause reframes the problem
20
+ - a workflow weakness is identified clearly
21
+ - a design/system principle is discovered that should constrain future work
22
+ - a repeated failure mode becomes understandable
23
+ - a strong product or design insight should influence later implementation and review
24
+
25
+ Do not log routine status updates, generic summaries, or low-signal chatter.
26
+
27
+ ## Required structure
28
+
29
+ Each entry in `AHA.md` should include:
30
+
31
+ 1. date
32
+ 2. title
33
+ 3. insight
34
+ 4. why it matters
35
+ 5. consequence for workflow / design / execution
36
+ 6. evidence anchors
37
+
38
+ Keep entries compact and forceful.
39
+
40
+ ## Workflow
41
+
42
+ 1. Inspect the current repo state and discussion context.
43
+ 2. Read existing `AHA.md` if present.
44
+ 3. Merge with the existing log instead of scattering similar insights.
45
+ 4. Add the new aha as a durable entry with anchors.
46
+ 5. If the insight changes operational expectations, mention which skills, docs, or gates should consume it.
47
+
48
+ ## Guardrails
49
+
50
+ - `AHA.md` is for durable realizations, not chronological journaling.
51
+ - Prefer one strong entry over several weak entries.
52
+ - Link the insight to exact files or contracts when possible.
53
+ - If an insight becomes obsolete, mark it superseded rather than silently deleting history.
54
+
55
+ ## Suggested format
56
+
57
+ Use this structure unless the project already has a stronger convention:
58
+
59
+ - title with project name
60
+ - `## Active Ahas`
61
+ - repeated `### YYYY-MM-DD — Title`
62
+ - short bullets:
63
+ - `Insight`
64
+ - `Why It Matters`
65
+ - `Action`
66
+ - `Evidence`
67
+
68
+ ## Script
69
+
70
+ The agent autonomously calls this helper to append structured aha entries:
71
+
72
+ ```bash
73
+ python3 ~/.codex/skills/xflow/aha/scripts/append_aha.py \
74
+ --title "Title" \
75
+ --insight "Core insight" \
76
+ --why "Why it matters" \
77
+ --action "Required action" \
78
+ --evidence "/abs/path/or/doc"
79
+ ```
80
+
81
+ Agent determines parameters from discussion context.
82
+ User invokes skill, agent handles execution.
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ from datetime import date
4
+ from pathlib import Path
5
+
6
+
7
+ def parse_args() -> argparse.Namespace:
8
+ parser = argparse.ArgumentParser(
9
+ description="Append a structured aha entry to the canonical root AHA.md.",
10
+ )
11
+ parser.add_argument("--project-root", default=".", help="Project root containing AHA.md.")
12
+ parser.add_argument("--title", required=True, help="Short aha title.")
13
+ parser.add_argument("--insight", required=True, help="Core insight.")
14
+ parser.add_argument("--why", required=True, help="Why this matters.")
15
+ parser.add_argument("--action", required=True, help="Required workflow/design action.")
16
+ parser.add_argument("--evidence", action="append", default=[], help="Evidence anchor. Can be repeated.")
17
+ return parser.parse_args()
18
+
19
+
20
+ def ensure_header(path: Path) -> None:
21
+ if path.exists():
22
+ return
23
+ path.write_text("# AHA\n\n## Active Ahas\n", encoding="utf-8")
24
+
25
+
26
+ def main() -> int:
27
+ args = parse_args()
28
+ root = Path(args.project_root).expanduser().resolve()
29
+ aha_path = root / "AHA.md"
30
+ ensure_header(aha_path)
31
+
32
+ entry_lines = [
33
+ "",
34
+ f"### {date.today().isoformat()} — {args.title.strip()}",
35
+ f"- Insight: {args.insight.strip()}",
36
+ f"- Why It Matters: {args.why.strip()}",
37
+ f"- Action: {args.action.strip()}",
38
+ ]
39
+ if args.evidence:
40
+ entry_lines.append("- Evidence:")
41
+ for item in args.evidence:
42
+ entry_lines.append(f" - {item.strip()}")
43
+
44
+ with aha_path.open("a", encoding="utf-8") as handle:
45
+ handle.write("\n".join(entry_lines) + "\n")
46
+ return 0
47
+
48
+
49
+ if __name__ == "__main__":
50
+ raise SystemExit(main())
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import json
4
+ import subprocess
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ import sys
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 assert_stage_transition_allowed, assert_status_transition_allowed, find_git_root, get_current_branch, get_remote_url, load_status, maybe_relative, now_iso, record_stage_transition, record_value_transition, resolve_project_context, run, save_status, set_project_single_select_status, upsert_issue_comment # noqa: E402
13
+
14
+
15
+ TARGETS = {
16
+ "product": "product.md",
17
+ "architecture": "architecture.md",
18
+ "workflow": "workflow.md",
19
+ "ux": "ux.md",
20
+ "design": "DESIGN.md",
21
+ }
22
+
23
+
24
+ def parse_args() -> argparse.Namespace:
25
+ parser = argparse.ArgumentParser(
26
+ description="Conservatively archive a Superflow change into root specs.",
27
+ )
28
+ parser.add_argument(
29
+ "--project-root",
30
+ default=".",
31
+ help="Project root containing specs/.",
32
+ )
33
+ parser.add_argument(
34
+ "--change-id",
35
+ required=True,
36
+ help="Change identifier under specs/changes/.",
37
+ )
38
+ parser.add_argument("--repo", help="Override target repository for issue/project close-out.")
39
+ parser.add_argument("--issue-number", type=int, help="Override linked issue number for close-out.")
40
+ parser.add_argument("--checkout-path", help="Override local checkout path for git commit/push.")
41
+ parser.add_argument("--commit-message", help="Commit message to use for archive publishing.")
42
+ parser.add_argument("--push", action="store_true", help="Push the current branch after commit.")
43
+ parser.add_argument("--allow-detached-branch-publish", action="store_true", help="Allow commit/push even when the current branch does not match the linked issue branch.")
44
+ parser.add_argument("--skip-doctor-precheck", action="store_true", help="Skip the automatic doctor precheck before archive publish.")
45
+ parser.add_argument("--close-issue", action="store_true", help="Close the linked issue after archive.")
46
+ parser.add_argument("--project-owner", help="GitHub project owner login for project status updates.")
47
+ parser.add_argument("--project-number", type=int, help="GitHub project number for project status updates.")
48
+ parser.add_argument("--status-field-name", default="Status", help="Project single select field name.")
49
+ parser.add_argument("--done-option-name", default="Done", help="Project status option name to apply.")
50
+ parser.add_argument("--merge-snippets-only", action="store_true", help="Only merge change-scoped merge-*.md snippets into root specs.")
51
+ return parser.parse_args()
52
+
53
+
54
+ def read_text(path: Path) -> str:
55
+ return path.read_text(encoding="utf-8")
56
+
57
+
58
+ def write_text(path: Path, content: str) -> None:
59
+ path.write_text(content, encoding="utf-8")
60
+
61
+
62
+ def stage_archive_paths(project_root: Path, specs_root: Path, change_root: Path) -> None:
63
+ candidates = [
64
+ specs_root,
65
+ project_root / "HANDOFF.md",
66
+ project_root / "AHA.md",
67
+ project_root / "DESIGN.md",
68
+ ]
69
+ existing = [str(path) for path in candidates if path.exists()]
70
+ if existing:
71
+ run(["git", "add", *existing], cwd=project_root)
72
+
73
+
74
+ def list_unstaged_tracked_changes(project_root: Path) -> list[str]:
75
+ result = subprocess.run(
76
+ ["git", "diff", "--name-only"],
77
+ cwd=str(project_root),
78
+ text=True,
79
+ capture_output=True,
80
+ check=True,
81
+ )
82
+ return [line.strip() for line in result.stdout.splitlines() if line.strip()]
83
+
84
+
85
+ def assert_no_unstaged_tracked_changes(project_root: Path) -> None:
86
+ paths = list_unstaged_tracked_changes(project_root)
87
+ if not paths:
88
+ return
89
+ joined = "\n- ".join(paths)
90
+ raise SystemExit(
91
+ "Refusing archive: unstaged tracked changes remain after staging archive paths. "
92
+ "Stage intended implementation/test changes before archive, or clean unrelated edits:\n"
93
+ f"- {joined}"
94
+ )
95
+
96
+
97
+ def refresh_handoff_current_state(project_root: Path, change_id: str) -> None:
98
+ script = Path(__file__).resolve().parents[2] / "handoff" / "scripts" / "scaffold_handoff.py"
99
+ if script.exists():
100
+ run([
101
+ sys.executable,
102
+ str(script),
103
+ "--project-root",
104
+ str(project_root),
105
+ "--refresh",
106
+ "--change-id",
107
+ change_id,
108
+ ], cwd=project_root)
109
+
110
+
111
+ def has_staged_changes(project_root: Path) -> bool:
112
+ result = subprocess.run(
113
+ ["git", "diff", "--cached", "--quiet"],
114
+ cwd=str(project_root),
115
+ text=True,
116
+ capture_output=True,
117
+ )
118
+ return result.returncode == 1
119
+
120
+
121
+ def append_change_block(target_path: Path, change_id: str, snippet_path: Path) -> bool:
122
+ snippet = read_text(snippet_path).strip()
123
+ if not snippet:
124
+ return False
125
+
126
+ existing = read_text(target_path) if target_path.exists() else ""
127
+ marker = f"## Change: {change_id}"
128
+ if marker in existing:
129
+ return False
130
+
131
+ timestamp = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
132
+ block = (
133
+ f"\n\n{marker}\n\n"
134
+ f"Source: `{snippet_path}`\n"
135
+ f"Archived: {timestamp}\n\n"
136
+ f"{snippet}\n"
137
+ )
138
+ if existing and not existing.endswith("\n"):
139
+ existing += "\n"
140
+ write_text(target_path, existing + block)
141
+ return True
142
+
143
+
144
+ def main() -> int:
145
+ args = parse_args()
146
+ project_root = Path(args.project_root).expanduser().resolve()
147
+ specs_root = project_root / "specs"
148
+ change_root = specs_root / "changes" / args.change_id
149
+
150
+ required_paths = [
151
+ change_root / "proposal.md",
152
+ change_root / "plan.md",
153
+ change_root / "tasks.md",
154
+ change_root / "status.json",
155
+ ]
156
+ missing = [str(path) for path in required_paths if not path.exists()]
157
+ if missing:
158
+ raise SystemExit("Missing required change artifacts:\n- " + "\n- ".join(missing))
159
+
160
+ status_path, status = load_status(project_root, args.change_id)
161
+
162
+ if not args.merge_snippets_only and status.get("current_stage") not in {"verify", "archive"} and status.get("status") not in {"done", "blocked"}:
163
+ raise SystemExit("Refusing archive: change still appears in flight.")
164
+
165
+ verification_status = status.get("verification_status")
166
+ if not args.merge_snippets_only and status.get("status") == "done" and verification_status != "passed":
167
+ raise SystemExit("Refusing archive: completed change must have verification_status=passed.")
168
+
169
+ # Check design alignment for frontend changes
170
+ change_type = status.get("change_type", "backend")
171
+ design_aligned = status.get("design_aligned", "na")
172
+ if not args.merge_snippets_only and change_type in ("frontend", "full-stack") and design_aligned not in ("yes", "na"):
173
+ if design_aligned == "no":
174
+ raise SystemExit("Refusing archive: frontend change has design_aligned=no. Resolve design drift first.")
175
+ elif design_aligned == "pending":
176
+ print("Warning: frontend change has design_aligned=pending. Proceeding but recommend design verification.")
177
+
178
+ if not args.merge_snippets_only and status.get("archival_status") == "archived":
179
+ raise SystemExit("Refusing archive: change is already archived.")
180
+
181
+ if not args.merge_snippets_only:
182
+ stage_archive_paths(project_root, specs_root, change_root)
183
+ assert_no_unstaged_tracked_changes(project_root)
184
+
185
+ updated_targets: list[str] = []
186
+ for key, filename in TARGETS.items():
187
+ snippet_path = change_root / f"merge-{key}.md"
188
+ if not snippet_path.exists():
189
+ continue
190
+ # DESIGN.md is at project root, others are in specs/
191
+ if key == "design":
192
+ target_path = project_root / filename
193
+ else:
194
+ target_path = specs_root / filename
195
+ if not target_path.exists():
196
+ # For design, create if snippet exists and frontend change
197
+ if key == "design" and change_type in ("frontend", "full-stack"):
198
+ # Copy template
199
+ template_path = Path(__file__).resolve().parents[2] / "yolo" / "templates" / "DESIGN.md"
200
+ if template_path.exists():
201
+ write_text(target_path, read_text(template_path))
202
+ else:
203
+ raise SystemExit(f"Missing DESIGN.md template at {template_path}")
204
+ else:
205
+ raise SystemExit(f"Missing target spec file for merge: {target_path}")
206
+ changed = append_change_block(target_path, args.change_id, snippet_path)
207
+ if changed:
208
+ updated_targets.append(str(target_path))
209
+
210
+ if args.merge_snippets_only:
211
+ print("Mode: merge-snippets-only")
212
+ print(f"Merged snippets for change: {args.change_id}")
213
+ if updated_targets:
214
+ print("Updated root specs:")
215
+ for target in updated_targets:
216
+ print(f"- {target}")
217
+ else:
218
+ print("No root specs updated: no merge-*.md snippets were present.")
219
+ return 0
220
+
221
+ assert_stage_transition_allowed(
222
+ status.get("current_stage"),
223
+ "archive",
224
+ current_status=status.get("status"),
225
+ allow_terminal_status_override=("done", "blocked"),
226
+ )
227
+ if status.get("status") == "done":
228
+ assert_status_transition_allowed(status.get("status"), "done", allow_terminal_status_override=("done",))
229
+ elif status.get("status") == "blocked":
230
+ assert_status_transition_allowed(status.get("status"), "blocked", allow_terminal_status_override=("blocked",))
231
+ else:
232
+ assert_status_transition_allowed(status.get("status"), "done")
233
+ record_stage_transition(status, "archive", reason="archive script began canonical close-out")
234
+ if status.get("status") != "blocked":
235
+ record_value_transition(status, "status", "done", reason="archive completed canonical close-out")
236
+ status["archival_status"] = "archived"
237
+ status["next_action"] = "Archive complete. Refresh HANDOFF.md and durable lessons if new information emerged."
238
+ save_status(status_path, status)
239
+ refresh_handoff_current_state(project_root, args.change_id)
240
+
241
+ repo = args.repo or status.get("issue_repo") or status.get("target_repo")
242
+ issue_number = args.issue_number or status.get("issue_number")
243
+ checkout_path = Path(args.checkout_path or status.get("checkout_path") or find_git_root(project_root))
244
+ linked_branch = status.get("branch_name")
245
+ commit_message = args.commit_message or f"archive(xflow): close {args.change_id}"
246
+
247
+ if repo and issue_number and linked_branch and not args.skip_doctor_precheck:
248
+ pre_result = run_doctor_checks(
249
+ project_root=project_root,
250
+ change_id=args.change_id,
251
+ repo=repo,
252
+ issue_number=int(issue_number),
253
+ branch_name=linked_branch,
254
+ checkout_path_override=checkout_path,
255
+ )
256
+ if not pre_result["ok"]:
257
+ raise SystemExit("Doctor precheck failed before archive publish. Run doctor.py for details.")
258
+
259
+ remote_url = None
260
+ if checkout_path:
261
+ remote_url = get_remote_url(checkout_path)
262
+
263
+ if linked_branch and not args.allow_detached_branch_publish:
264
+ current_branch = get_current_branch(checkout_path)
265
+ if current_branch != linked_branch:
266
+ raise SystemExit(
267
+ f"Refusing publish: current branch is {current_branch!r}, but linked issue branch is {linked_branch!r}. "
268
+ "Switch to the linked branch or pass --allow-detached-branch-publish."
269
+ )
270
+
271
+ stage_archive_paths(project_root, specs_root, change_root)
272
+ commit_result = "no staged changes"
273
+ if has_staged_changes(project_root):
274
+ run(["git", "commit", "-m", commit_message], cwd=checkout_path)
275
+ commit_result = commit_message
276
+
277
+ push_result = "skipped"
278
+ if args.push:
279
+ run(["git", "push"], cwd=checkout_path)
280
+ push_result = "pushed"
281
+
282
+ project_context = resolve_project_context(repo, int(issue_number), args.project_owner, args.project_number) if repo and issue_number else None
283
+ project_result = None
284
+ if repo and issue_number and project_context:
285
+ project_result = set_project_single_select_status(
286
+ project_context["owner"],
287
+ int(project_context["number"]),
288
+ args.status_field_name,
289
+ args.done_option_name,
290
+ repo,
291
+ int(issue_number),
292
+ )
293
+ elif repo and issue_number:
294
+ upsert_issue_comment(
295
+ repo,
296
+ int(issue_number),
297
+ "<!-- xflow:archive-state -->",
298
+ "Archive completed. No GitHub Project context was provided, so Superflow recorded completion via issue comment fallback.",
299
+ )
300
+
301
+ if repo and issue_number and args.close_issue:
302
+ run(["gh", "issue", "close", str(issue_number), "--repo", repo], cwd=checkout_path)
303
+
304
+ status["git_publish"] = {
305
+ "commit": commit_result,
306
+ "push": push_result,
307
+ "remote_url": remote_url,
308
+ }
309
+ if project_result:
310
+ status["project_done"] = {
311
+ "owner": project_context["owner"],
312
+ "number": int(project_context["number"]),
313
+ "field_name": args.status_field_name,
314
+ "option_name": args.done_option_name,
315
+ "source": project_context.get("source", "explicit"),
316
+ **project_result,
317
+ }
318
+ save_status(status_path, status)
319
+
320
+ print("Mode: full-publish")
321
+ print(f"Archived change: {args.change_id}")
322
+ if updated_targets:
323
+ print("Updated root specs:")
324
+ for target in updated_targets:
325
+ print(f"- {target}")
326
+ else:
327
+ print("No root specs updated: no merge-*.md snippets were present.")
328
+ print(f"Commit: {commit_result}")
329
+ print(f"Push: {push_result}")
330
+ if project_result:
331
+ print(f"Project status: {args.done_option_name}")
332
+ elif repo and issue_number:
333
+ print("Project status: issue comment fallback")
334
+ return 0
335
+
336
+
337
+ if __name__ == "__main__":
338
+ raise SystemExit(main())
@@ -0,0 +1,54 @@
1
+ # Design Merge Snippet
2
+
3
+ > This file contains design decisions to merge into `DESIGN.md` or `specs/ux.md` during archive.
4
+ > Place in `specs/changes/<change-id>/merge-design.md`.
5
+
6
+ ## Change Summary
7
+
8
+ - Change ID: [change-id]
9
+ - Date: [YYYY-MM-DD]
10
+ - Title: [change title]
11
+
12
+ ## Design Decisions
13
+
14
+ ### Visual Tone
15
+ - [decision made, if changed]
16
+ - [reasoning]
17
+
18
+ ### Color System
19
+ - [colors added/changed]
20
+ - [reasoning]
21
+
22
+ ### Typography
23
+ - [fonts added/changed]
24
+ - [reasoning]
25
+
26
+ ### Layout & Spacing
27
+ - [spacing changes]
28
+ - [reasoning]
29
+
30
+ ### Motion
31
+ - [animation changes]
32
+ - [reasoning]
33
+
34
+ ### Component Patterns
35
+ - [new patterns added]
36
+ - [reasoning]
37
+
38
+ ## Design Decisions Log Entry
39
+
40
+ Add this to the Design Decisions Log section:
41
+
42
+ ```
43
+ - [YYYY-MM-DD] [change-id]: [key design decision] — [reasoning]
44
+ ```
45
+
46
+ ## Verification
47
+
48
+ - Design alignment verified: [yes/no]
49
+ - Visual comparison done: [yes/no]
50
+ - Matches DESIGN.md specifications: [yes/no/partial]
51
+
52
+ ## Notes
53
+
54
+ [Any additional design-related notes for future reference]
@@ -0,0 +1,26 @@
1
+ # Workflow Merge Snippet
2
+
3
+ > This file contains workflow or capability deltas to merge into `specs/workflow.md` during archive.
4
+ > Place in `specs/changes/<change-id>/merge-workflow.md`.
5
+
6
+ ## Change Summary
7
+
8
+ - Change ID: [change-id]
9
+ - Date: [YYYY-MM-DD]
10
+ - Title: [change title]
11
+
12
+ ## Capability Delta
13
+
14
+ - [what capability or boundary changed]
15
+
16
+ ## Behavior Changes
17
+
18
+ - [what behavior changed]
19
+
20
+ ## Constraints and Invariants
21
+
22
+ - [what remains true]
23
+
24
+ ## Migration Notes
25
+
26
+ - [any follow-up required to keep workflow/specs aligned]
@@ -0,0 +1,6 @@
1
+ """
2
+ Superflow atom entry points — thin wrappers over existing implementation scripts.
3
+
4
+ Each module here corresponds to exactly one atom ID in atoms/registry.json.
5
+ The implementations live in ../scripts/ and ../*/scripts/ and are NOT duplicated here.
6
+ """