aw-spec-cli 0.1.28 → 0.1.30
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/assets/.amp/agent.json +22 -0
- package/assets/.amp/settings-edit.json +5 -0
- package/assets/.amp/settings-read.json +12 -0
- package/assets/.appweaver/DESIGN-FORMAT.md +9 -0
- package/assets/.appweaver/STACK-PROFILES.md +102 -15
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +40 -18
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +56 -4
- package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +17 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +12 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +289 -24
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +370 -85
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +349 -65
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +39 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +63 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +5 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +430 -138
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +392 -76
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +104 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +20 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +26 -3
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +314 -44
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +249 -20
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +17 -2
- package/assets/.appweaver/scripts/python/aw-plangate.py +109 -19
- package/assets/.appweaver/scripts/python/aw-stack.py +1295 -9
- package/assets/.appweaver/templates/delta-design-template.md +7 -5
- package/assets/.appweaver/templates/delta-spec-template.md +2 -0
- package/assets/.appweaver/templates/design-template.md +17 -0
- package/assets/.appweaver/templates/ui-design-template.md +19 -4
- package/assets/.augment/agent.json +22 -0
- package/assets/.augment/commands/appweaver.md +12 -0
- package/assets/.cline/agent.json +22 -0
- package/assets/.clinerules/workflows/appweaver.md +7 -0
- package/assets/.codex/agent.json +22 -0
- package/assets/.continue/agent.json +22 -0
- package/assets/.continue/prompts/appweaver.md +13 -0
- package/assets/.cursor/agent.json +22 -0
- package/assets/.cursor/commands/appweaver.md +7 -0
- package/assets/.devin/agent.json +22 -0
- package/assets/.devin/workflows/appweaver.md +11 -0
- package/assets/.factory/agent.json +22 -0
- package/assets/.factory/commands/appweaver.md +12 -0
- package/assets/.gemini/agent.json +22 -0
- package/assets/.github/prompts/appweaver.prompt.md +13 -0
- package/assets/.kilo/agent.json +28 -0
- package/assets/.kilo/agents/appweaver-edit.md +12 -0
- package/assets/.kilo/agents/appweaver-read.md +12 -0
- package/assets/.kilo/commands/appweaver.md +11 -0
- package/assets/.kiro/agent.json +22 -0
- package/assets/.kiro/skills/appweaver/SKILL.md +12 -0
- package/assets/.opencode/agent.json +22 -0
- package/assets/.opencode/agents/appweaver-edit.md +12 -0
- package/assets/.opencode/agents/appweaver-read.md +13 -0
- package/assets/.opencode/commands/appweaver.md +11 -0
- package/assets/.roo/agent.json +22 -0
- package/assets/.roo/commands/appweaver.md +12 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +14 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/copy-assets.d.ts +16 -3
- package/dist/utils/copy-assets.d.ts.map +1 -1
- package/dist/utils/copy-assets.js +29 -14
- package/dist/utils/copy-assets.js.map +1 -1
- package/package.json +1 -1
|
@@ -37,22 +37,39 @@
|
|
|
37
37
|
# (SOLE WRITER of .appweaver/stacks.json; callers = select-stack, onboard)
|
|
38
38
|
# lane-resolve [--kind <k>] [--id <lane>] [--feature-dir <path>]
|
|
39
39
|
# -> {declared, lanes[], found, lane, root, source_path, build, test, run,
|
|
40
|
-
# backend_*/frontend_* (declared, root, source_path, root_exists,
|
|
41
|
-
# feature_excluded), feature_lanes[], prototype
|
|
40
|
+
# backend_*/frontend_* (declared, working, root, source_path, root_exists,
|
|
41
|
+
# build, test, run, feature_excluded), feature_lanes[], prototype,
|
|
42
|
+
# working_lane, working_kind, working_kinds[], working_root, working_test_path}
|
|
42
43
|
# (absence is a valid state: no stacks.json -> declared:false, exit 0;
|
|
43
|
-
# --feature-dir reports a lane the feature records no surface in as not declared
|
|
44
|
-
#
|
|
45
|
-
#
|
|
44
|
+
# --feature-dir reports a lane the feature records no surface in as not declared;
|
|
45
|
+
# *_working = declared AND in this developer's .appweaver/lane.json)
|
|
46
|
+
# lane-verify [--require backend|frontend]… [--allow-missing-root] [--working-only] [--command <phase>]
|
|
47
|
+
# -> {ok, declared, issues[], issue_count, message, backend_*/frontend_*, working_*}
|
|
46
48
|
# (the GATE: is the declaration usable for the phase about to run.
|
|
47
|
-
# Always exits 0 — the workflow's verify_context owns the stop
|
|
49
|
+
# Always exits 0 — the workflow's verify_context owns the stop;
|
|
50
|
+
# --working-only checks on disk only the working lanes' root folders)
|
|
51
|
+
# lane-select [--lane backend|frontend|both] [--default] [--preselect <text>]
|
|
52
|
+
# -> {lane, kinds[], source, note, default, select_options[], preselect, written}
|
|
53
|
+
# (the developer's WORKING lane in .appweaver/lane.json, not committed;
|
|
54
|
+
# --default = both when both lanes are declared, else the one that is)
|
|
48
55
|
# lane-probe [--kind <k>] [--hint <text>] -> {declared, declared_profile, hint_profile, hint_terms[], signals[], any, evidence}
|
|
49
56
|
# (the FILE signals for "does this lane exist"; resolve-tech carries the rule signal)
|
|
50
57
|
# lane-set (--id <lane>|--kind <k>) [--set KEY=VALUE]… [--unset KEY]… -> {ok, lane_id, set, removed, written}
|
|
51
58
|
# (KEY: contract_run, contract, client; an empty VALUE changes nothing)
|
|
52
59
|
# contract-export [--timeout <s>] -> {ok, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}
|
|
53
60
|
# (start the service lane with contract_run, save the document at contract, stop it)
|
|
54
|
-
# contract-
|
|
55
|
-
#
|
|
61
|
+
# contract-draft --feature-dir <p> -> {ok, written, removed, path, plan, operations, ids[], ids_csv, unread_count, reason}
|
|
62
|
+
# (the feature's api-draft.json, drafted by script from its backend plan's API section)
|
|
63
|
+
# contract-combine -> {ok, doc, export, export_exists, drafts[], added, added_csv, served_csv, clashes_csv, status, reason}
|
|
64
|
+
# (.appweaver/contracts/openapi.combined.json: the export plus every drafted operation it does not serve)
|
|
65
|
+
# contract-requests --feature-dir <p> -> {count, open_count, open_csv, declined_csv, rebind_csv, backend_text, frontend_text, summary, requests[]}
|
|
66
|
+
# (the frontend plan's RQ-n requests and the backend plan's answers)
|
|
67
|
+
# contract-client [--doc <d>] -> run-phase result for the frontend lane's `client` command ({{contract}} = --doc)
|
|
68
|
+
# contract-check --plan <p> [--doc <d>] -> {ok, cited, ids[], missing[], drafted[], uncited, doc_exists, reason}
|
|
69
|
+
# contract-impact --delta-backend <p> --frontend-plan <p> [--delta-frontend <p>]
|
|
70
|
+
# -> {changes[], affected[], addressed[], unaddressed[], unaddressed_csv, warn}
|
|
71
|
+
# (operations a change's service delta changes or removes that the frontend binds to,
|
|
72
|
+
# with no matching change in its frontend delta plan)
|
|
56
73
|
# list-features [--preselect <name>] [--bundle <dir>] -> {features[], names[], count, select_options[], sentinel} (specs/ ∪ features the OKF concepts serve)
|
|
57
74
|
# set-feature --feature <name> -> {feature, feature_dir, spec_file, existed, created, spec_exists}
|
|
58
75
|
# default-scope --context <name> --feature <name> -> {created, skipped_reason, context_dir, feature_dir}
|
|
@@ -114,7 +131,8 @@
|
|
|
114
131
|
# build-compile [--feature-dir <path>] [--kind backend|frontend]
|
|
115
132
|
# -> {ok, tool, ran, cmd, log_tail}
|
|
116
133
|
# (--kind builds that one declared lane; ignored when no lane is declared)
|
|
117
|
-
# test-run [--feature-dir <path>]
|
|
134
|
+
# test-run [--feature-dir <path>] [--scope slice|repo] [--also <csv>]… [--kind backend|frontend]
|
|
135
|
+
# -> {ok, tool, ran, cmd, log_tail,
|
|
118
136
|
# failing_excerpt, failing_lanes[],
|
|
119
137
|
# no_setup, setup_reason, setup_missing[]}
|
|
120
138
|
# (failing_excerpt = the lines that NAME the failure, per failing lane;
|
|
@@ -125,17 +143,32 @@
|
|
|
125
143
|
# -> {cr_id, cr_dir, delta_spec, delta_model, wave, open_count, created,
|
|
126
144
|
# created_on, base_version, base_context_version}
|
|
127
145
|
# cr-list [--feature-dir <path>] [--preselect <cr-id|main|new>] [--include-main] [--include-new]
|
|
128
|
-
#
|
|
129
|
-
#
|
|
146
|
+
# [--lane backend|frontend|both]
|
|
147
|
+
# -> {changes[] (each with lanes[], lanes_recorded), ids[], count,
|
|
148
|
+
# select_options[], matched_label, main_label, new_label, only_id,
|
|
149
|
+
# active_change, lane_filter[], hidden_ids[], hidden_count,
|
|
150
|
+
# hidden_ids_csv, preselect_hidden}
|
|
151
|
+
# (--lane lists only the changes touching one of those lanes; none = every open change)
|
|
130
152
|
# cr-paths --cr-id <id> [--feature-dir <path>]
|
|
131
153
|
# -> {cr_dir, delta_spec, delta_model, delta_backend_plan, delta_frontend_plan,
|
|
132
|
-
# wave,
|
|
133
|
-
#
|
|
154
|
+
# wave, lanes[], lanes_csv, lanes_recorded, touches_backend,
|
|
155
|
+
# touches_frontend, predecessor_files[], predecessor_specs[],
|
|
156
|
+
# predecessor_docs[], sibling_files[], domain_model}
|
|
134
157
|
# cr-overlap --cr-id <id> [--feature-dir <path>]
|
|
135
158
|
# -> {aggregates[], overlaps[], overlap_count, predecessor_overlaps,
|
|
136
159
|
# sibling_overlaps, successor_overlaps, overlap_report}
|
|
137
160
|
# cr-activate --cr-id <id> [--feature-dir <path>] -> {active_change, activated, open_changes[]}
|
|
138
161
|
# cr-wave --cr-id <id> --wave <n> [--feature-dir <path>] -> {cr_id, wave, stamped}
|
|
162
|
+
# cr-lanes [--cr-id <id> [--feature-dir <path>] [--stamp] [--set both|backend|frontend]]
|
|
163
|
+
# -> {select_options[], has_options, default, recorded, recorded_kinds[],
|
|
164
|
+
# lanes[], lanes_csv, backend, frontend, source, stamped}
|
|
165
|
+
# (the lanes a change touches, on its delta-spec's **Lanes** line; no line = every declared lane)
|
|
166
|
+
# slice-gate-stamp --cr-id <id> --lane backend|frontend [--feature-dir <path>] [--tests-green true|false]
|
|
167
|
+
# [--tests-ran true|false] [--requirements-ok true|false] [--findings <json list>]
|
|
168
|
+
# -> {file, lane, cr_id, verdict, tests_green, tests_ran,
|
|
169
|
+
# requirements_ok, findings, high_findings, gated}
|
|
170
|
+
# (one lane's analyze-slice-delta result, as changes/<id>/.aw-slice-gate.<lane>.json;
|
|
171
|
+
# pass = no test failed and the requirements met, tests_ran = a suite actually ran)
|
|
139
172
|
# cr-archive --cr-id <id> [--feature-dir <path>] [--recall] [--reason <text>]
|
|
140
173
|
# -> {ok, archived_dir, archived_model, recalled,
|
|
141
174
|
# recall_marker, version, previous,
|
|
@@ -858,10 +891,11 @@ cmd_lane_set() {
|
|
|
858
891
|
_aw_stack_py set "${fwd[@]}"
|
|
859
892
|
}
|
|
860
893
|
|
|
861
|
-
# ─────────────── contract-export /
|
|
894
|
+
# ─────────────── contract-export / -draft / -combine / -requests / -client / -check ───────────────
|
|
862
895
|
# The API contract between the lanes: export the service lane's API document from the
|
|
863
|
-
# running service,
|
|
864
|
-
#
|
|
896
|
+
# running service, draft a feature's document from its backend plan, combine the two for
|
|
897
|
+
# the frontend, read the request ledger between the plans, generate the frontend lane's
|
|
898
|
+
# client, and check the operation ids a frontend plan cites. All delegate to aw-stack.py.
|
|
865
899
|
cmd_contract_export() {
|
|
866
900
|
local -a fwd=()
|
|
867
901
|
while [ $# -gt 0 ]; do
|
|
@@ -874,14 +908,53 @@ cmd_contract_export() {
|
|
|
874
908
|
_aw_stack_py contract-export "${fwd[@]}"
|
|
875
909
|
}
|
|
876
910
|
|
|
911
|
+
cmd_contract_draft() {
|
|
912
|
+
local -a fwd=()
|
|
913
|
+
while [ $# -gt 0 ]; do
|
|
914
|
+
case "$1" in
|
|
915
|
+
--feature-dir) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
916
|
+
--help|-h) echo "contract-draft --feature-dir <path> -> {ok, written, removed, path, plan, operations, ids, ids_csv, unread_count, reason}"; return 0 ;;
|
|
917
|
+
*) echo "contract-draft: unknown arg '$1'" >&2; return 1 ;;
|
|
918
|
+
esac
|
|
919
|
+
done
|
|
920
|
+
_aw_stack_py contract-draft "${fwd[@]+"${fwd[@]}"}"
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
cmd_contract_combine() {
|
|
924
|
+
while [ $# -gt 0 ]; do
|
|
925
|
+
case "$1" in
|
|
926
|
+
--help|-h) echo "contract-combine -> {ok, doc, export, export_exists, drafts, added, added_csv, served_csv, clashes_csv, status, reason}"; return 0 ;;
|
|
927
|
+
*) echo "contract-combine: unknown arg '$1'" >&2; return 1 ;;
|
|
928
|
+
esac
|
|
929
|
+
done
|
|
930
|
+
_aw_stack_py contract-combine
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
cmd_contract_requests() {
|
|
934
|
+
local -a fwd=()
|
|
935
|
+
while [ $# -gt 0 ]; do
|
|
936
|
+
case "$1" in
|
|
937
|
+
--feature-dir) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
938
|
+
--help|-h) echo "contract-requests --feature-dir <path> -> {count, open_count, open_csv, declined_csv, rebind_csv, backend_text, frontend_text, summary, requests}"; return 0 ;;
|
|
939
|
+
*) echo "contract-requests: unknown arg '$1'" >&2; return 1 ;;
|
|
940
|
+
esac
|
|
941
|
+
done
|
|
942
|
+
_aw_stack_py contract-requests "${fwd[@]+"${fwd[@]}"}"
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
# --doc names the document `{{contract}}` stands for in the lane's `client` command — the
|
|
946
|
+
# combined document, from implement. Without it, the exported one. An empty --doc is
|
|
947
|
+
# dropped, so a workflow can render the argument whole.
|
|
877
948
|
cmd_contract_client() {
|
|
949
|
+
local -a fwd=()
|
|
878
950
|
while [ $# -gt 0 ]; do
|
|
879
951
|
case "$1" in
|
|
880
|
-
--
|
|
952
|
+
--doc) if [ -n "$2" ]; then fwd+=("--contract" "$2"); fi; shift 2 ;;
|
|
953
|
+
--help|-h) echo "contract-client [--doc <api document>] -> run-phase --phase client for the frontend lane"; return 0 ;;
|
|
881
954
|
*) echo "contract-client: unknown arg '$1'" >&2; return 1 ;;
|
|
882
955
|
esac
|
|
883
956
|
done
|
|
884
|
-
_aw_stack_py run-phase --phase client --kind frontend
|
|
957
|
+
_aw_stack_py run-phase --phase client --kind frontend "${fwd[@]+"${fwd[@]}"}"
|
|
885
958
|
}
|
|
886
959
|
|
|
887
960
|
cmd_contract_check() {
|
|
@@ -896,6 +969,21 @@ cmd_contract_check() {
|
|
|
896
969
|
_aw_stack_py contract-check "${fwd[@]}"
|
|
897
970
|
}
|
|
898
971
|
|
|
972
|
+
# What a change's service delta changes or removes that the frontend master plan binds to,
|
|
973
|
+
# and whether the change's frontend delta plan carries it. Each path is forwarded only when
|
|
974
|
+
# it is not empty.
|
|
975
|
+
cmd_contract_impact() {
|
|
976
|
+
local -a fwd=()
|
|
977
|
+
while [ $# -gt 0 ]; do
|
|
978
|
+
case "$1" in
|
|
979
|
+
--delta-backend|--frontend-plan|--delta-frontend) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
980
|
+
--help|-h) echo "contract-impact --delta-backend <p> --frontend-plan <p> [--delta-frontend <p>] -> {changes[], changed_csv, affected[], addressed[], unaddressed[], unaddressed_count, unaddressed_csv, warn}"; return 0 ;;
|
|
981
|
+
*) echo "contract-impact: unknown arg '$1'" >&2; return 1 ;;
|
|
982
|
+
esac
|
|
983
|
+
done
|
|
984
|
+
_aw_stack_py contract-impact "${fwd[@]+"${fwd[@]}"}"
|
|
985
|
+
}
|
|
986
|
+
|
|
899
987
|
# The gate: is the declaration USABLE for the phase about to run. --require names a
|
|
900
988
|
# lane kind the phase cannot run without and is repeatable; every DECLARED lane is
|
|
901
989
|
# checked for a sound lane root folder whether or not it was required, because a stack
|
|
@@ -916,13 +1004,32 @@ cmd_lane_verify() {
|
|
|
916
1004
|
# --flag=value: --command carries a phase name that starts with '/'.
|
|
917
1005
|
--command) fwd+=("--command=$2"); shift 2 ;;
|
|
918
1006
|
--allow-missing-root) fwd+=("--allow-missing-root"); shift ;;
|
|
919
|
-
--
|
|
1007
|
+
--working-only) fwd+=("--working-only"); shift ;;
|
|
1008
|
+
--help|-h) echo "lane-verify [--require backend|frontend]… [--allow-missing-root] [--working-only] [--command <phase>]"; return 0 ;;
|
|
920
1009
|
*) echo "lane-verify: unknown arg '$1'" >&2; return 1 ;;
|
|
921
1010
|
esac
|
|
922
1011
|
done
|
|
923
1012
|
_aw_stack_py verify "${fwd[@]}"
|
|
924
1013
|
}
|
|
925
1014
|
|
|
1015
|
+
# The developer's WORKING lane, in .appweaver/lane.json (not committed). No argument
|
|
1016
|
+
# reads it and lists the answers the declared stacks offer; --lane writes one of them;
|
|
1017
|
+
# --default writes the default for the declared stacks (select-stack's call).
|
|
1018
|
+
cmd_lane_select() {
|
|
1019
|
+
local -a fwd=()
|
|
1020
|
+
while [ $# -gt 0 ]; do
|
|
1021
|
+
case "$1" in
|
|
1022
|
+
# --flag=value: --preselect carries raw user prose, and a value that starts
|
|
1023
|
+
# with '-' reads as another flag when passed separately.
|
|
1024
|
+
--lane|--preselect) fwd+=("$1=$2"); shift 2 ;;
|
|
1025
|
+
--default) fwd+=("--default"); shift ;;
|
|
1026
|
+
--help|-h) echo "lane-select [--lane backend|frontend|both] [--default] [--preselect <text>] -> {lane, kinds[], source, note, default, select_options[], preselect, written}"; return 0 ;;
|
|
1027
|
+
*) echo "lane-select: unknown arg '$1'" >&2; return 1 ;;
|
|
1028
|
+
esac
|
|
1029
|
+
done
|
|
1030
|
+
_aw_stack_py lane-select "${fwd[@]}"
|
|
1031
|
+
}
|
|
1032
|
+
|
|
926
1033
|
# Evidence that a lane of this kind exists, from the FILE signals only: a lane in
|
|
927
1034
|
# stacks.json, and a stack name appearing in free prose. The authoritative signal
|
|
928
1035
|
# — whether the active project's technical map carries `## /aw-spec-<command>` for the
|
|
@@ -3338,13 +3445,17 @@ cmd_test_run() {
|
|
|
3338
3445
|
# are computed separately — the referrer closure and the features that co-own the
|
|
3339
3446
|
# aggregates the change touches — and the dialect cannot concatenate two variables into
|
|
3340
3447
|
# one argument, so the caller passes each list as its own --also.
|
|
3341
|
-
|
|
3448
|
+
# --kind runs ONE declared lane's suite — the developer's working lane, so a backend
|
|
3449
|
+
# developer's gate is not failed by the frontend's tests, nor its fix loop sent into
|
|
3450
|
+
# the frontend's code. Empty means every declared lane, as before.
|
|
3451
|
+
local feature_dir="" scope="repo" also="" kind=""
|
|
3342
3452
|
while [ $# -gt 0 ]; do
|
|
3343
3453
|
case "$1" in
|
|
3344
3454
|
--feature-dir) feature_dir="$2"; shift 2 ;;
|
|
3345
3455
|
--scope) scope="$2"; shift 2 ;;
|
|
3346
3456
|
--also) also="${also:+$also,}$2"; shift 2 ;;
|
|
3347
|
-
--
|
|
3457
|
+
--kind) kind="$2"; shift 2 ;;
|
|
3458
|
+
--help|-h) echo "test-run [--feature-dir <path>] [--scope slice|repo] [--also <csv>]... [--kind backend|frontend] (--also repeatable; lists union)"; return 0 ;;
|
|
3348
3459
|
*) echo "test-run: unknown arg '$1'" >&2; return 1 ;;
|
|
3349
3460
|
esac
|
|
3350
3461
|
done
|
|
@@ -3387,13 +3498,17 @@ cmd_test_run() {
|
|
|
3387
3498
|
# slice run delegates and widens to the lane's whole suite, which the fence tolerates
|
|
3388
3499
|
# by design — it over-includes rather than risking an under-scoped miss — and reports
|
|
3389
3500
|
# scoped=false so fence_green comes from `ok` rather than from the zero-match guard.
|
|
3501
|
+
# A FRONTEND-only run always delegates: the .NET path below runs the service's suite,
|
|
3502
|
+
# which is not the lane being asked about.
|
|
3390
3503
|
local use_lanes=false
|
|
3504
|
+
local -a kind_args=()
|
|
3505
|
+
if [ -n "$kind" ]; then kind_args=("--kind" "$kind"); fi
|
|
3391
3506
|
if [ -n "$(_aw_lanes_declared)" ]; then
|
|
3392
|
-
if [ "$scope" != "slice" ] || [ "$dotnet_present" = false ]; then use_lanes=true; fi
|
|
3507
|
+
if [ "$scope" != "slice" ] || [ "$dotnet_present" = false ] || [ "$kind" = "frontend" ]; then use_lanes=true; fi
|
|
3393
3508
|
fi
|
|
3394
3509
|
if [ "$use_lanes" = true ]; then
|
|
3395
3510
|
rm -f "$log"
|
|
3396
|
-
_aw_stack_py run-phase --phase test | AW_SCOPE="$scope" AW_SLICES="$slices" python3 -c '
|
|
3511
|
+
_aw_stack_py run-phase --phase test "${kind_args[@]}" | AW_SCOPE="$scope" AW_SLICES="$slices" python3 -c '
|
|
3397
3512
|
import json, os, re, sys
|
|
3398
3513
|
d = json.loads(sys.stdin.readline() or "{}")
|
|
3399
3514
|
log, ok = d.get("log_tail", ""), bool(d.get("ok"))
|
|
@@ -3738,6 +3853,63 @@ cmd_cr_wave() {
|
|
|
3738
3853
|
"$(json_escape "$dspec")" "$(json_escape "$dmodel")"
|
|
3739
3854
|
}
|
|
3740
3855
|
|
|
3856
|
+
# ─────────────────────────────── cr-lanes ───────────────────────────────────
|
|
3857
|
+
# The lanes a change touches, on its delta-spec's `**Lanes**:` line. No --cr-id lists the
|
|
3858
|
+
# answers the declared stacks offer (both first); with --cr-id it reads that change's line,
|
|
3859
|
+
# and --set / --stamp write it — the way specify and plan record their answer, and the way
|
|
3860
|
+
# to widen or narrow an open change afterwards. The lane logic is aw-stack.py's; this
|
|
3861
|
+
# resolves the change to its delta-spec.
|
|
3862
|
+
cmd_cr_lanes() {
|
|
3863
|
+
local cr_id="" fd_arg="" answer="" stamp=""
|
|
3864
|
+
while [ $# -gt 0 ]; do
|
|
3865
|
+
case "$1" in
|
|
3866
|
+
--cr-id) cr_id="$2"; shift 2 ;;
|
|
3867
|
+
--feature-dir) fd_arg="$2"; shift 2 ;;
|
|
3868
|
+
--set) answer="$2"; shift 2 ;;
|
|
3869
|
+
--stamp) stamp=1; shift ;;
|
|
3870
|
+
--help|-h) echo "cr-lanes [--cr-id <id> [--feature-dir <path>] [--stamp] [--set both|backend|frontend]] -> {select_options[], has_options, default, recorded, lanes[], lanes_csv, backend, frontend, source, stamped}"; return 0 ;;
|
|
3871
|
+
*) echo "cr-lanes: unknown arg '$1'" >&2; return 1 ;;
|
|
3872
|
+
esac
|
|
3873
|
+
done
|
|
3874
|
+
cr_id=$(_cr_id_token "$cr_id")
|
|
3875
|
+
local -a fwd=()
|
|
3876
|
+
if [ -n "$cr_id" ]; then
|
|
3877
|
+
local repo_root; repo_root=$(get_repo_root)
|
|
3878
|
+
local fd; fd=$(_resolve_feature_dir "$fd_arg" "$repo_root") || { echo "cr-lanes: no feature set and no --feature-dir" >&2; return 1; }
|
|
3879
|
+
fwd+=("--delta-spec" "$fd/changes/$cr_id/delta-spec.md")
|
|
3880
|
+
elif [ -n "$stamp$answer" ]; then
|
|
3881
|
+
echo "cr-lanes: --set and --stamp need --cr-id" >&2; return 1
|
|
3882
|
+
fi
|
|
3883
|
+
[ -n "$stamp" ] && fwd+=("--stamp")
|
|
3884
|
+
# --flag=value: the answer may arrive as a whole option line, spaces and all.
|
|
3885
|
+
[ -n "$answer" ] && fwd+=("--set=$answer")
|
|
3886
|
+
_aw_stack_py cr-lanes "${fwd[@]+"${fwd[@]}"}"
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
# ─────────────────────────── slice-gate-stamp ──────────────────────────────
|
|
3890
|
+
# analyze-slice-delta's result for ONE lane, written into the change's directory as
|
|
3891
|
+
# .aw-slice-gate.<lane>.json and committed with it. The rule lives in python/aw-stack.py;
|
|
3892
|
+
# this resolves the change's directory and hands over.
|
|
3893
|
+
cmd_slice_gate_stamp() {
|
|
3894
|
+
local cr_id="" fd_arg=""
|
|
3895
|
+
local -a fwd=()
|
|
3896
|
+
while [ $# -gt 0 ]; do
|
|
3897
|
+
case "$1" in
|
|
3898
|
+
--cr-id) cr_id="$2"; shift 2 ;;
|
|
3899
|
+
--feature-dir) fd_arg="$2"; shift 2 ;;
|
|
3900
|
+
# --flag=value: the findings are a JSON list, and any value may be empty.
|
|
3901
|
+
--lane|--tests-green|--tests-ran|--requirements-ok|--findings) fwd+=("$1=$2"); shift 2 ;;
|
|
3902
|
+
--help|-h) echo "slice-gate-stamp --cr-id <id> --lane backend|frontend [--feature-dir <path>] [--tests-green true|false] [--tests-ran true|false] [--requirements-ok true|false] [--findings <json list>] -> {file, lane, cr_id, verdict, tests_green, tests_ran, requirements_ok, findings, high_findings, gated}"; return 0 ;;
|
|
3903
|
+
*) echo "slice-gate-stamp: unknown arg '$1'" >&2; return 1 ;;
|
|
3904
|
+
esac
|
|
3905
|
+
done
|
|
3906
|
+
cr_id=$(_cr_id_token "$cr_id")
|
|
3907
|
+
[ -n "$cr_id" ] || { echo "slice-gate-stamp: --cr-id is required" >&2; return 1; }
|
|
3908
|
+
local repo_root; repo_root=$(get_repo_root)
|
|
3909
|
+
local fd; fd=$(_resolve_feature_dir "$fd_arg" "$repo_root") || { echo "slice-gate-stamp: no feature set and no --feature-dir" >&2; return 1; }
|
|
3910
|
+
_aw_stack_py slice-gate-stamp --cr-dir "$fd/changes/$cr_id" --cr-id "$cr_id" "${fwd[@]+"${fwd[@]}"}"
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3741
3913
|
# ──────────────────────────── cr-wave-derive ───────────────────────────────
|
|
3742
3914
|
# THE WAVE A CHANGE'S OWN DOCUMENTS IMPLY, from their `## Depends on` sections.
|
|
3743
3915
|
#
|
|
@@ -4114,15 +4286,24 @@ PY
|
|
|
4114
4286
|
# new` resolves to it.
|
|
4115
4287
|
# `count` and `has_changes` keep counting REAL open changes, so a caller that gates on
|
|
4116
4288
|
# them is unaffected by either flag.
|
|
4289
|
+
#
|
|
4290
|
+
# EACH CHANGE'S LANES, from its delta-spec's `**Lanes**:` line (`lanes[]`, and
|
|
4291
|
+
# `lanes_recorded` false when there is no line, which means every lane). `--lane backend |
|
|
4292
|
+
# frontend | both` lists only the changes that touch one of those lanes; every count, id,
|
|
4293
|
+
# option and preselect above then covers that list alone, and `hidden_ids[]` names the open
|
|
4294
|
+
# changes it left out. No `--lane`, or an empty one, lists every open change. A change that
|
|
4295
|
+
# touches one lane only says so in its option label; the label of one touching both is
|
|
4296
|
+
# unchanged.
|
|
4117
4297
|
cmd_cr_list() {
|
|
4118
|
-
local feature_dir_arg="" preselect="" inc_main="" inc_new=""
|
|
4298
|
+
local feature_dir_arg="" preselect="" inc_main="" inc_new="" lane=""
|
|
4119
4299
|
while [ $# -gt 0 ]; do
|
|
4120
4300
|
case "$1" in
|
|
4121
4301
|
--feature-dir) feature_dir_arg="$2"; shift 2 ;;
|
|
4122
4302
|
--preselect) preselect="$2"; shift 2 ;;
|
|
4123
4303
|
--include-main) inc_main="1"; shift ;;
|
|
4124
4304
|
--include-new) inc_new="1"; shift ;;
|
|
4125
|
-
--
|
|
4305
|
+
--lane) lane="$2"; shift 2 ;;
|
|
4306
|
+
--help|-h) echo "cr-list [--feature-dir <path>] [--preselect <cr-id|main|new>] [--include-main] [--include-new] [--lane backend|frontend|both] -> {changes[], ids[], count, select_options[], matched_label, main_label, new_label, active_change, lane_filter[], hidden_ids[], hidden_count}"; return 0 ;;
|
|
4126
4307
|
*) echo "cr-list: unknown arg '$1'" >&2; return 1 ;;
|
|
4127
4308
|
esac
|
|
4128
4309
|
done
|
|
@@ -4130,8 +4311,9 @@ cmd_cr_list() {
|
|
|
4130
4311
|
local fd; fd=$(_resolve_feature_dir "$feature_dir_arg" "$repo_root") || { echo "cr-list: no feature set and no --feature-dir" >&2; return 1; }
|
|
4131
4312
|
local ctx_dir; ctx_dir=$(_context_dir_for_feature "$fd" "$repo_root")
|
|
4132
4313
|
AW_FD="$fd" AW_CTX="$ctx_dir" AW_ROOT="$repo_root" AW_PRE="$(_cr_id_token "$preselect")" \
|
|
4133
|
-
AW_PRE_RAW="$preselect" AW_INC_MAIN="$inc_main" AW_INC_NEW="$inc_new"
|
|
4134
|
-
|
|
4314
|
+
AW_PRE_RAW="$preselect" AW_INC_MAIN="$inc_main" AW_INC_NEW="$inc_new" AW_LANE="$lane" \
|
|
4315
|
+
AW_STACK_PY="$SCRIPT_DIR/../python/aw-stack.py" python3 - <<'PY'
|
|
4316
|
+
import json, os, re, sys
|
|
4135
4317
|
fd = os.environ["AW_FD"]; ctx = os.environ["AW_CTX"]; root = os.environ["AW_ROOT"]
|
|
4136
4318
|
pre = os.environ.get("AW_PRE", "").strip()
|
|
4137
4319
|
# The WHOLE argument, not its first token. A cr id is matched on the token because a
|
|
@@ -4158,6 +4340,27 @@ def title_of(path):
|
|
|
4158
4340
|
m = re.match(r"^#\s+.*?:\s*(.+)$", first)
|
|
4159
4341
|
t = m.group(1).strip() if m else ""
|
|
4160
4342
|
return "" if (not t or t.startswith("[")) else t
|
|
4343
|
+
# The lanes a change touches: read_change_lanes in python/aw-stack.py, the one reader of a
|
|
4344
|
+
# delta-spec's **Lanes** line. None when the line is missing or still the template
|
|
4345
|
+
# placeholder — a change touching every lane.
|
|
4346
|
+
import importlib.util
|
|
4347
|
+
try:
|
|
4348
|
+
_spec = importlib.util.spec_from_file_location("aw_stack", os.environ["AW_STACK_PY"])
|
|
4349
|
+
_aw_stack = importlib.util.module_from_spec(_spec)
|
|
4350
|
+
_spec.loader.exec_module(_aw_stack)
|
|
4351
|
+
except Exception as _e:
|
|
4352
|
+
sys.stderr.write("cr-list: cannot load %s: %s\n" % (os.environ.get("AW_STACK_PY", ""), _e))
|
|
4353
|
+
sys.exit(1)
|
|
4354
|
+
lanes_of = _aw_stack.read_change_lanes
|
|
4355
|
+
want = []
|
|
4356
|
+
for tok in re.split(r"[\s,]+", os.environ.get("AW_LANE", "").strip().lower()):
|
|
4357
|
+
if not tok: continue
|
|
4358
|
+
if tok == "both": want += ["backend", "frontend"]
|
|
4359
|
+
elif tok in ("backend", "frontend"): want.append(tok)
|
|
4360
|
+
else:
|
|
4361
|
+
sys.stderr.write("cr-list: --lane takes backend, frontend or both, not '%s'\n" % tok)
|
|
4362
|
+
sys.exit(1)
|
|
4363
|
+
want = [k for k in ("backend", "frontend") if k in want]
|
|
4161
4364
|
tracked = []
|
|
4162
4365
|
avp = os.path.join(fd, "active_version.json")
|
|
4163
4366
|
active = ""
|
|
@@ -4181,6 +4384,7 @@ if os.path.isdir(changes_dir):
|
|
|
4181
4384
|
wave = header(dspec, "Wave") or header(dmodel, "Wave") or "1"
|
|
4182
4385
|
try: wave = int(re.sub(r"\D", "", wave) or 1)
|
|
4183
4386
|
except Exception: wave = 1
|
|
4387
|
+
lanes = lanes_of(dspec)
|
|
4184
4388
|
rows.append({
|
|
4185
4389
|
"cr_id": name, "cr_dir": rel(d), "wave": wave,
|
|
4186
4390
|
"title": title_of(dspec),
|
|
@@ -4193,11 +4397,17 @@ if os.path.isdir(changes_dir):
|
|
|
4193
4397
|
"legacy_delta_design": os.path.isfile(legacy),
|
|
4194
4398
|
"tracked": name in tracked,
|
|
4195
4399
|
"active": name == active,
|
|
4400
|
+
"lanes": lanes or ["backend", "frontend"],
|
|
4401
|
+
"lanes_recorded": lanes is not None,
|
|
4196
4402
|
})
|
|
4197
4403
|
rows.sort(key=lambda r: (r["wave"], r["cr_id"]))
|
|
4404
|
+
hidden = [r for r in rows if want and not set(r["lanes"]) & set(want)]
|
|
4405
|
+
hidden_set = set(r["cr_id"] for r in hidden)
|
|
4406
|
+
rows = [r for r in rows if r["cr_id"] not in hidden_set]
|
|
4198
4407
|
def label(r):
|
|
4199
4408
|
bits = [r["cr_id"], "wave %d" % r["wave"]]
|
|
4200
4409
|
bits.append(r["title"] or "(delta-spec not authored yet)")
|
|
4410
|
+
if len(r["lanes"]) == 1: bits.append("%s only" % r["lanes"][0])
|
|
4201
4411
|
planned = [k for k, v in (("backend", r["delta_backend_plan_exists"]), ("frontend", r["delta_frontend_plan_exists"])) if v]
|
|
4202
4412
|
if planned: bits.append("planned: " + "+".join(planned))
|
|
4203
4413
|
return " · ".join(bits)
|
|
@@ -4229,6 +4439,12 @@ print(json.dumps({
|
|
|
4229
4439
|
# The lone open change, so a caller can take it without a prompt when there is
|
|
4230
4440
|
# nothing to choose between.
|
|
4231
4441
|
"only_id": rows[0]["cr_id"] if len(rows) == 1 else "",
|
|
4442
|
+
# What --lane left out: open changes that touch none of the lanes asked for.
|
|
4443
|
+
"lane_filter": want,
|
|
4444
|
+
"hidden_ids": [r["cr_id"] for r in hidden], "hidden_count": len(hidden),
|
|
4445
|
+
"hidden_ids_csv": ", ".join("%s (%s only)" % (r["cr_id"], "+".join(r["lanes"])) for r in hidden),
|
|
4446
|
+
# A --preselect naming one of those, so a caller can say why it was not selected.
|
|
4447
|
+
"preselect_hidden": pre if (pre and not matched and pre in hidden_set) else "",
|
|
4232
4448
|
}))
|
|
4233
4449
|
PY
|
|
4234
4450
|
}
|
|
@@ -4249,7 +4465,7 @@ cmd_cr_paths() {
|
|
|
4249
4465
|
case "$1" in
|
|
4250
4466
|
--cr-id) cr_id="$2"; shift 2 ;;
|
|
4251
4467
|
--feature-dir) feature_dir_arg="$2"; shift 2 ;;
|
|
4252
|
-
--help|-h) echo "cr-paths --cr-id <id> [--feature-dir <path>] -> {cr_id, cr_dir, delta_spec, delta_model, delta_backend_plan, delta_frontend_plan, wave, predecessor_files[], predecessor_specs[], predecessor_docs[], sibling_files[], domain_model}"; return 0 ;;
|
|
4468
|
+
--help|-h) echo "cr-paths --cr-id <id> [--feature-dir <path>] -> {cr_id, cr_dir, delta_spec, delta_model, delta_backend_plan, delta_frontend_plan, wave, lanes[], lanes_csv, lanes_recorded, touches_backend, touches_frontend, predecessor_files[], predecessor_specs[], predecessor_docs[], sibling_files[], domain_model}"; return 0 ;;
|
|
4253
4469
|
*) echo "cr-paths: unknown arg '$1'" >&2; return 1 ;;
|
|
4254
4470
|
esac
|
|
4255
4471
|
done
|
|
@@ -4258,8 +4474,9 @@ cmd_cr_paths() {
|
|
|
4258
4474
|
local repo_root; repo_root=$(get_repo_root)
|
|
4259
4475
|
local fd; fd=$(_resolve_feature_dir "$feature_dir_arg" "$repo_root") || { echo "cr-paths: no feature set and no --feature-dir" >&2; return 1; }
|
|
4260
4476
|
local ctx_dir; ctx_dir=$(_context_dir_for_feature "$fd" "$repo_root")
|
|
4261
|
-
AW_FD="$fd" AW_CTX="$ctx_dir" AW_ROOT="$repo_root" AW_CR="$cr_id"
|
|
4262
|
-
|
|
4477
|
+
AW_FD="$fd" AW_CTX="$ctx_dir" AW_ROOT="$repo_root" AW_CR="$cr_id" \
|
|
4478
|
+
AW_STACK_PY="$SCRIPT_DIR/../python/aw-stack.py" python3 - <<'PY'
|
|
4479
|
+
import json, os, re, glob, sys
|
|
4263
4480
|
fd = os.environ["AW_FD"]; ctx = os.environ["AW_CTX"]; root = os.environ["AW_ROOT"]; cr = os.environ["AW_CR"]
|
|
4264
4481
|
def rel(p): return os.path.relpath(p, root)
|
|
4265
4482
|
def wave_of(path, default=1):
|
|
@@ -4274,6 +4491,19 @@ be = os.path.join(cr_dir, "delta-backend-plan.md")
|
|
|
4274
4491
|
fe = os.path.join(cr_dir, "delta-frontend-plan.md")
|
|
4275
4492
|
master = os.path.join(ctx, "domain-model.md")
|
|
4276
4493
|
wave = wave_of(dspec, wave_of(dmodel, 1))
|
|
4494
|
+
# The lanes a change touches: read_change_lanes in python/aw-stack.py, the one reader of a
|
|
4495
|
+
# delta-spec's **Lanes** line. None when the line is missing or still the template
|
|
4496
|
+
# placeholder — a change touching every lane.
|
|
4497
|
+
import importlib.util
|
|
4498
|
+
try:
|
|
4499
|
+
_spec = importlib.util.spec_from_file_location("aw_stack", os.environ["AW_STACK_PY"])
|
|
4500
|
+
_aw_stack = importlib.util.module_from_spec(_spec)
|
|
4501
|
+
_spec.loader.exec_module(_aw_stack)
|
|
4502
|
+
except Exception as _e:
|
|
4503
|
+
sys.stderr.write("cr-paths: cannot load %s: %s\n" % (os.environ.get("AW_STACK_PY", ""), _e))
|
|
4504
|
+
sys.exit(1)
|
|
4505
|
+
recorded = _aw_stack.read_change_lanes(dspec)
|
|
4506
|
+
lanes = recorded or ["backend", "frontend"]
|
|
4277
4507
|
# A change's model delta sits at CONTEXT level and its delta-spec at FEATURE level, so the
|
|
4278
4508
|
# spec of another open change is found by walking the context's features rather than
|
|
4279
4509
|
# assuming it belongs to this one — two features of one context can both have changes open.
|
|
@@ -4302,6 +4532,10 @@ print(json.dumps({
|
|
|
4302
4532
|
"delta_backend_plan": rel(be), "delta_backend_plan_exists": os.path.isfile(be),
|
|
4303
4533
|
"delta_frontend_plan": rel(fe), "delta_frontend_plan_exists": os.path.isfile(fe),
|
|
4304
4534
|
"wave": wave,
|
|
4535
|
+
# The lanes this change touches, from its delta-spec's **Lanes** line — every lane when
|
|
4536
|
+
# it records none (lanes_recorded false).
|
|
4537
|
+
"lanes": lanes, "lanes_csv": ",".join(lanes), "lanes_recorded": recorded is not None,
|
|
4538
|
+
"touches_backend": "backend" in lanes, "touches_frontend": "frontend" in lanes,
|
|
4305
4539
|
"context_dir": rel(ctx),
|
|
4306
4540
|
"domain_model": rel(master), "domain_model_exists": os.path.isfile(master),
|
|
4307
4541
|
# Lower wave = committed to landing first, so this delta may plan on top of it.
|
|
@@ -5051,17 +5285,24 @@ _plan_gate_file() { printf '%s/.aw-plan-gate.json' "$1"; }
|
|
|
5051
5285
|
# an untagged gap included — is a plan gap. Unreadable input is one plan gap: a verdict that
|
|
5052
5286
|
# could not be read must not pass.
|
|
5053
5287
|
cmd_plan_gaps() {
|
|
5054
|
-
|
|
5288
|
+
# --lanes is the developer's WORKING lanes (csv). A plan-route gap tagged with a lane
|
|
5289
|
+
# outside it belongs to the developer who owns that lane: it is listed under `other`
|
|
5290
|
+
# and neither drives this run's repair nor its stop. A gap routed to specify stays
|
|
5291
|
+
# routed whatever lane it was found in — the spec is shared. With no --lanes every
|
|
5292
|
+
# lane is worked, which is the behaviour from before the option existed.
|
|
5293
|
+
local gaps="" lanes=""
|
|
5055
5294
|
while [ $# -gt 0 ]; do
|
|
5056
5295
|
case "$1" in
|
|
5057
5296
|
--gaps) gaps="$2"; shift 2 ;;
|
|
5058
|
-
--
|
|
5297
|
+
--lanes) lanes="$2"; shift 2 ;;
|
|
5298
|
+
--help|-h) echo "plan-gaps --gaps <json list> [--lanes backend,frontend] -> {verdict, plan_ok, plan_gaps, routed_gaps, routed[], routed_csv, other_gaps, other[], other_csv, backend_verdict, backend_plan_gaps, backend_routed[], frontend_verdict, frontend_plan_gaps, frontend_routed[]}"; return 0 ;;
|
|
5059
5299
|
*) echo "plan-gaps: unknown arg '$1'" >&2; return 1 ;;
|
|
5060
5300
|
esac
|
|
5061
5301
|
done
|
|
5062
|
-
AW_GAPS="$gaps" python3 - <<'PY'
|
|
5302
|
+
AW_GAPS="$gaps" AW_LANES="$lanes" python3 - <<'PY'
|
|
5063
5303
|
import json, os
|
|
5064
5304
|
raw = (os.environ.get("AW_GAPS") or "").strip()
|
|
5305
|
+
lanes = [l.strip().lower() for l in (os.environ.get("AW_LANES") or "").split(",") if l.strip()]
|
|
5065
5306
|
try:
|
|
5066
5307
|
gaps = json.loads(raw) if raw else []
|
|
5067
5308
|
if not isinstance(gaps, list):
|
|
@@ -5069,27 +5310,46 @@ try:
|
|
|
5069
5310
|
readable = True
|
|
5070
5311
|
except Exception:
|
|
5071
5312
|
gaps, readable = [], False
|
|
5072
|
-
plan, routed = 0, []
|
|
5313
|
+
plan, routed, other = 0, [], []
|
|
5314
|
+
per = {"backend": [0, []], "frontend": [0, []]}
|
|
5073
5315
|
for g in gaps:
|
|
5074
5316
|
d = g if isinstance(g, dict) else {"gap": str(g)}
|
|
5075
5317
|
text = str(d.get("gap", "")).strip()
|
|
5318
|
+
lane = str(d.get("lane", "") or "").strip().lower()
|
|
5319
|
+
ref = str(d.get("spec_ref", "") or "").strip()
|
|
5320
|
+
line = ("%s: %s" % (ref, text)) if ref and ref != "—" else text
|
|
5321
|
+
targets = [lane] if lane in per else list(per)
|
|
5076
5322
|
if text.upper().startswith("LIFT-FAILURE") or str(d.get("route", "")).strip().lower() == "specify":
|
|
5077
|
-
|
|
5078
|
-
|
|
5323
|
+
routed.append(line)
|
|
5324
|
+
for t in targets:
|
|
5325
|
+
per[t][1].append(line)
|
|
5326
|
+
elif lanes and lane in per and lane not in lanes:
|
|
5327
|
+
other.append("%s: %s" % (lane, line))
|
|
5079
5328
|
else:
|
|
5080
5329
|
plan += 1
|
|
5330
|
+
for t in targets:
|
|
5331
|
+
per[t][0] += 1
|
|
5081
5332
|
if not readable:
|
|
5082
5333
|
plan += 1
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5334
|
+
for t in per:
|
|
5335
|
+
per[t][0] += 1
|
|
5336
|
+
def verdict(p, r):
|
|
5337
|
+
return "gaps" if p else ("routed-gaps" if r else "conformant")
|
|
5338
|
+
out = {"ok": True, "readable": readable, "verdict": verdict(plan, routed), "plan_ok": plan == 0,
|
|
5339
|
+
"plan_gaps": plan, "routed_gaps": len(routed), "routed": routed,
|
|
5340
|
+
"routed_csv": "; ".join(routed), "lanes": lanes,
|
|
5341
|
+
"other_gaps": len(other), "other": other, "other_csv": "; ".join(other)}
|
|
5342
|
+
for k in ("backend", "frontend"):
|
|
5343
|
+
out[k + "_verdict"] = verdict(per[k][0], per[k][1])
|
|
5344
|
+
out[k + "_plan_gaps"] = per[k][0]
|
|
5345
|
+
out[k + "_routed"] = per[k][1]
|
|
5346
|
+
print(json.dumps(out))
|
|
5087
5347
|
PY
|
|
5088
5348
|
}
|
|
5089
5349
|
|
|
5090
5350
|
cmd_plan_gate_stamp() {
|
|
5091
5351
|
case "${1:-}" in
|
|
5092
|
-
--help|-h) echo "plan-gate-stamp [--feature-dir <path>] [--spec-version <v>] [--mode <text>] [--verdict conformant|routed-gaps|gaps] [--plan-gaps <n>] [--routed <json list>] -> {file, gated, verdict, hashed[], count}"; return 0 ;;
|
|
5352
|
+
--help|-h) echo "plan-gate-stamp [--feature-dir <path>] [--lane backend|frontend] [--spec-version <v>] [--mode <text>] [--verdict conformant|routed-gaps|gaps] [--plan-gaps <n>] [--routed <json list>] -> {file, lane, gated, verdict, hashed[], count}"; return 0 ;;
|
|
5093
5353
|
esac
|
|
5094
5354
|
local feature_dir_arg="" rest=()
|
|
5095
5355
|
while [ $# -gt 0 ]; do
|
|
@@ -5108,11 +5368,12 @@ cmd_plan_gate_stamp() {
|
|
|
5108
5368
|
}
|
|
5109
5369
|
|
|
5110
5370
|
cmd_plan_gate_check() {
|
|
5111
|
-
local feature_dir_arg=""
|
|
5371
|
+
local feature_dir_arg="" lane=""
|
|
5112
5372
|
while [ $# -gt 0 ]; do
|
|
5113
5373
|
case "$1" in
|
|
5114
5374
|
--feature-dir) feature_dir_arg="$2"; shift 2 ;;
|
|
5115
|
-
--
|
|
5375
|
+
--lane) lane="$2"; shift 2 ;;
|
|
5376
|
+
--help|-h) echo "plan-gate-check [--feature-dir <path>] [--lane backend|frontend] -> {lane, shared, tracked, gate_fresh, verdict, may_generate, plan_gaps, routed_gaps[], routed_csv, spec_moved, model_moved, plans_changed[], plans_changed_csv, gated, reason}"; return 0 ;;
|
|
5116
5377
|
*) echo "plan-gate-check: unknown arg '$1'" >&2; return 1 ;;
|
|
5117
5378
|
esac
|
|
5118
5379
|
done
|
|
@@ -5122,7 +5383,9 @@ cmd_plan_gate_check() {
|
|
|
5122
5383
|
local core="$SCRIPT_DIR/../python/aw-plangate.py"
|
|
5123
5384
|
if [ ! -f "$core" ]; then echo "plan-gate-check: core not found at $core" >&2; return 1; fi
|
|
5124
5385
|
if ! command -v python3 >/dev/null 2>&1; then echo "plan-gate-check: python3 is required" >&2; return 1; fi
|
|
5125
|
-
|
|
5386
|
+
local -a lane_args=()
|
|
5387
|
+
if [ -n "$lane" ]; then lane_args=("--lane" "$lane"); fi
|
|
5388
|
+
python3 "$core" check --feature-dir "$fd" --context-dir "$ctx_dir" --root "$repo_root" "${lane_args[@]+"${lane_args[@]}"}"
|
|
5126
5389
|
}
|
|
5127
5390
|
|
|
5128
5391
|
# ─────────────────────────────── gap-route ─────────────────────────────────
|
|
@@ -5280,9 +5543,14 @@ case "$sub" in
|
|
|
5280
5543
|
lane-verify) cmd_lane_verify "$@" ;;
|
|
5281
5544
|
lane-probe) cmd_lane_probe "$@" ;;
|
|
5282
5545
|
lane-set) cmd_lane_set "$@" ;;
|
|
5546
|
+
lane-select) cmd_lane_select "$@" ;;
|
|
5283
5547
|
contract-export) cmd_contract_export "$@" ;;
|
|
5548
|
+
contract-draft) cmd_contract_draft "$@" ;;
|
|
5549
|
+
contract-combine) cmd_contract_combine "$@" ;;
|
|
5550
|
+
contract-requests) cmd_contract_requests "$@" ;;
|
|
5284
5551
|
contract-client) cmd_contract_client "$@" ;;
|
|
5285
5552
|
contract-check) cmd_contract_check "$@" ;;
|
|
5553
|
+
contract-impact) cmd_contract_impact "$@" ;;
|
|
5286
5554
|
list-contexts) cmd_list_contexts "$@" ;;
|
|
5287
5555
|
set-context) cmd_set_context "$@" ;;
|
|
5288
5556
|
list-features) cmd_list_features "$@" ;;
|
|
@@ -5309,6 +5577,8 @@ case "$sub" in
|
|
|
5309
5577
|
cr-overlap) cmd_cr_overlap "$@" ;;
|
|
5310
5578
|
cr-activate) cmd_cr_activate "$@" ;;
|
|
5311
5579
|
cr-wave) cmd_cr_wave "$@" ;;
|
|
5580
|
+
cr-lanes) cmd_cr_lanes "$@" ;;
|
|
5581
|
+
slice-gate-stamp) cmd_slice_gate_stamp "$@" ;;
|
|
5312
5582
|
cr-wave-derive) cmd_cr_wave_derive "$@" ;;
|
|
5313
5583
|
cr-rebase) cmd_cr_rebase "$@" ;;
|
|
5314
5584
|
cr-archive) cmd_cr_archive "$@" ;;
|