aw-spec-cli 0.1.14 → 0.1.16

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 (32) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +1 -1
  2. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +87 -0
  3. package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
  4. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
  5. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +672 -124
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +700 -72
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +531 -135
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +573 -145
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +600 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +721 -0
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +119 -32
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +1147 -0
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1121 -0
  18. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2564 -38
  19. package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
  20. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +2458 -22
  21. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
  22. package/assets/.appweaver/scripts/python/aw-payload-parity.py +158 -0
  23. package/assets/.appweaver/scripts/python/aw-xref.py +431 -48
  24. package/assets/.appweaver/templates/design-template.md +88 -54
  25. package/assets/.appweaver/templates/spec-template.md +12 -10
  26. package/assets/.appweaver/templates/story-template.md +83 -12
  27. package/assets/.appweaver/templates/ui-design-template.md +71 -39
  28. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
  29. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
  30. package/package.json +1 -1
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
@@ -6,9 +6,10 @@
6
6
  #
7
7
  # aw-xref.ps1 record --using X --design <design.md> [--origin ...] [--date YYYY-MM-DD] [--root DIR]
8
8
  # aw-xref.ps1 list --aggregate Y [--root DIR] [--bundle knowledge]
9
- # aw-xref.ps1 concept-upsert --aggregate X [--source-files a,b,c] [--description D] [--origin ...] [--root DIR]
9
+ # aw-xref.ps1 concept-upsert --aggregate X [--source-files a,b,c] [--lane backend|frontend] [--merge] [--description D] [--origin ...] [--root DIR]
10
10
  # aw-xref.ps1 concept-overview --aggregate X (--text PROSE | --text-file F) [--root DIR]
11
11
  # aw-xref.ps1 concept-paths --aggregate X | --feature-dir <dir> [--root DIR] [--bundle knowledge]
12
+ # aw-xref.ps1 concept-coverage --aggregate X | --feature-dir <dir> [--ids "FR-001,BR-002"] [--max-list N] [--root DIR] [--bundle knowledge]
12
13
  $ErrorActionPreference = "Stop"
13
14
  $core = Join-Path $PSScriptRoot "..\python\aw-xref.py"
14
15
  & python3 $core @args
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env python3
2
+ """aw-payload-parity.py — prove the two shells run the SAME python.
3
+
4
+ Most of aw-spec-actions is one python program embedded twice: as a `<<'PY'` heredoc in
5
+ aw-spec-actions.sh and as a `$PyName = @'…'@` here-string in aw-spec-actions.ps1. The
6
+ contract is that each pair is BYTE-IDENTICAL, and nothing checked it — so a fix applied to
7
+ one shell and forgotten in the other is a gate that behaves differently on Windows, in
8
+ silence. Three of the ops with no twin at all are on the forward chain (resolve-hooks, with
9
+ 17 call sites across the nine workflows, and checklist-read / checklist-apply, which produce
10
+ analyze-design's and analyze-slice's LIFT-FAILURE findings), and every one of them was read
11
+ in bash when the workflows were reviewed.
12
+
13
+ WHAT IT CHECKS
14
+ 1. Every bash payload has a byte-identical PowerShell twin.
15
+ 2. Every PowerShell payload has a byte-identical bash twin.
16
+ Payloads with no twin are listed, not counted against a hard-coded number: the split is
17
+ whatever the files say today, and pinning it would need editing on every legitimate change.
18
+
19
+ DUAL-AUTHORED BY DECISION. The ops in ACCEPTED_BASH_ONLY are bash-only on purpose (their
20
+ PowerShell path is written natively rather than shared) and are reported separately, so the
21
+ exit code reacts only to a NEW divergence. Moving one out of that list means it gained a
22
+ shared payload; adding one means the decision was taken to dual-author it — and either way
23
+ the reviewer has to say so in the same commit.
24
+
25
+ exit 0 every payload is paired, or unpaired only by accepted decision
26
+ exit 1 a new unpaired payload, or a pair that has drifted apart
27
+
28
+ Usage: aw-payload-parity.py [--root DIR] [--verbose]
29
+ """
30
+
31
+ import argparse
32
+ import hashlib
33
+ import os
34
+ import re
35
+ import sys
36
+
37
+ SH_REL = ".appweaver/scripts/bash/aw-spec-actions.sh"
38
+ PS_REL = ".appweaver/scripts/powershell/aw-spec-actions.ps1"
39
+
40
+ # Bash payloads with no shared PowerShell twin BY DECISION, not by oversight. Each is
41
+ # implemented natively on the PowerShell side and the two must be reviewed in pairs.
42
+ ACCEPTED_BASH_ONLY = {
43
+ "cmd_resolve_hooks",
44
+ "cmd_list_features",
45
+ "cmd_checklist_read",
46
+ "cmd_checklist_apply",
47
+ "cmd_compose_constitution",
48
+ "cmd_constitution_verify",
49
+ "cmd_component_verify",
50
+ "cmd_cr_archive",
51
+ "cmd_version_stamp",
52
+ }
53
+
54
+
55
+ def bash_payloads(text):
56
+ """[(owning function, payload, first line, last line)] in file order."""
57
+ out, owner, lines, i = [], "(top level)", text.split("\n"), 0
58
+ while i < len(lines):
59
+ m = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\(\)\s*\{", lines[i])
60
+ if m:
61
+ owner = m.group(1)
62
+ if re.search(r"<<'PY'\s*$", lines[i]):
63
+ j, body = i + 1, []
64
+ while j < len(lines) and lines[j] != "PY":
65
+ body.append(lines[j])
66
+ j += 1
67
+ out.append((owner, "\n".join(body), i + 2, j))
68
+ i = j
69
+ i += 1
70
+ return out
71
+
72
+
73
+ def ps_payloads(text):
74
+ """[(variable name, payload, first line, last line)] in file order."""
75
+ out, lines, i = [], text.split("\n"), 0
76
+ while i < len(lines):
77
+ m = re.match(r"^\$(Py[A-Za-z0-9_]*)\s*=\s*@'\s*$", lines[i])
78
+ if m:
79
+ j, body = i + 1, []
80
+ while j < len(lines) and lines[j] != "'@":
81
+ body.append(lines[j])
82
+ j += 1
83
+ out.append((m.group(1), "\n".join(body), i + 2, j))
84
+ i = j
85
+ i += 1
86
+ return out
87
+
88
+
89
+ def digest(s):
90
+ return hashlib.sha256(s.encode("utf-8")).hexdigest()
91
+
92
+
93
+ def main():
94
+ ap = argparse.ArgumentParser(description="check bash/PowerShell python-payload parity")
95
+ ap.add_argument("--root", default="", help="kit root (default: this script's kit)")
96
+ ap.add_argument("--verbose", action="store_true", help="list every matched pair too")
97
+ args = ap.parse_args()
98
+
99
+ root = args.root or os.path.normpath(
100
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", ".."))
101
+ sh_path, ps_path = os.path.join(root, SH_REL), os.path.join(root, PS_REL)
102
+ for p in (sh_path, ps_path):
103
+ if not os.path.isfile(p):
104
+ print("aw-payload-parity: not found: %s" % p, file=sys.stderr)
105
+ return 2
106
+
107
+ sh = bash_payloads(open(sh_path, encoding="utf-8").read())
108
+ ps = ps_payloads(open(ps_path, encoding="utf-8").read())
109
+
110
+ ps_by_hash = {}
111
+ for name, body, a, b in ps:
112
+ ps_by_hash.setdefault(digest(body), []).append((name, a, b))
113
+ sh_by_hash = {}
114
+ for owner, body, a, b in sh:
115
+ sh_by_hash.setdefault(digest(body), []).append((owner, a, b))
116
+
117
+ paired, accepted, unpaired_sh, unpaired_ps = [], [], [], []
118
+ for owner, body, a, b in sh:
119
+ twin = ps_by_hash.get(digest(body))
120
+ if twin:
121
+ paired.append((owner, twin[0][0]))
122
+ elif owner in ACCEPTED_BASH_ONLY:
123
+ accepted.append((owner, a, b))
124
+ else:
125
+ unpaired_sh.append((owner, a, b))
126
+ for name, body, a, b in ps:
127
+ if not sh_by_hash.get(digest(body)):
128
+ unpaired_ps.append((name, a, b))
129
+
130
+ print("payload parity: %d bash, %d powershell, %d byte-identical pair(s)"
131
+ % (len(sh), len(ps), len(paired)))
132
+ if args.verbose:
133
+ for owner, name in paired:
134
+ print(" ok %-34s == $%s" % (owner, name))
135
+ if accepted:
136
+ print("\ndual-authored by decision (%d) — PowerShell implements these natively, so a "
137
+ "change to one must be mirrored by hand:" % len(accepted))
138
+ for owner, a, b in accepted:
139
+ print(" -- %-34s %s:%d-%d" % (owner, os.path.basename(sh_path), a, b))
140
+
141
+ failed = bool(unpaired_sh or unpaired_ps)
142
+ if unpaired_sh:
143
+ print("\nFAIL — bash payloads with no byte-identical PowerShell twin:")
144
+ for owner, a, b in unpaired_sh:
145
+ print(" !! %-34s %s:%d-%d" % (owner, os.path.basename(sh_path), a, b))
146
+ if unpaired_ps:
147
+ print("\nFAIL — PowerShell payloads with no byte-identical bash twin:")
148
+ for name, a, b in unpaired_ps:
149
+ print(" !! $%-33s %s:%d-%d" % (name, os.path.basename(ps_path), a, b))
150
+ if failed:
151
+ print("\nA payload changed in one shell and not the other, or a new op shipped to one "
152
+ "shell only. Copy the payload verbatim into its twin — they are byte-identical "
153
+ "by contract — or, if the op is deliberately dual-authored, add its function to "
154
+ "ACCEPTED_BASH_ONLY in this file and say why in the commit.")
155
+ return 1 if failed else 0
156
+
157
+
158
+ sys.exit(main())