aw-spec-cli 0.1.33 → 0.1.34
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/README.md +76 -81
- package/assets/.appweaver/DESIGN-FORMAT.md +57 -25
- package/assets/.appweaver/STACK-PROFILES.md +95 -60
- package/assets/.appweaver/orchestrator/LAUNCHER.md +1 -1
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +173 -32
- package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +29 -1
- package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +7 -4
- package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +49 -60
- package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +58 -0
- package/assets/.appweaver/orchestrator/orchestrator.py +26 -8
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +20 -20
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -48
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +9 -7
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -5
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +239 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +254 -43
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +248 -68
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +251 -20
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +316 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +6 -47
- package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +62 -16
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +132 -9
- package/assets/.appweaver/orchestrator/workflows/aw-spec-fix-test-coverage.orch.yaml +965 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +222 -169
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +21 -22
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +453 -120
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +2 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +83 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +45 -2
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +324 -74
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +275 -44
- package/assets/.appweaver/scripts/python/aw-foundation.py +16 -13
- package/assets/.appweaver/scripts/python/aw-gaps.py +11 -3
- package/assets/.appweaver/scripts/python/aw-model-guard.py +295 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +412 -3
- package/assets/.appweaver/scripts/python/aw-plangate.py +23 -16
- package/assets/.appweaver/scripts/python/aw-stack.py +1794 -265
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +0 -2
- package/assets/.appweaver/stack-profiles/java-spring.json +0 -2
- package/assets/.appweaver/templates/api-plan-template.md +107 -0
- package/assets/.appweaver/templates/delta-api-plan-template.md +87 -0
- package/assets/.appweaver/templates/delta-design-template.md +25 -23
- package/assets/.appweaver/templates/delta-spec-template.md +3 -3
- package/assets/.appweaver/templates/design-template.md +20 -42
- package/assets/.appweaver/templates/domain-model-template.md +3 -0
- package/assets/.appweaver/templates/ui-delta-design-template.md +11 -8
- package/assets/.appweaver/templates/ui-design-template.md +4 -4
- package/assets/foundations/foundation.spec.template.txt +25 -18
- package/assets/guardrails/README.md +61 -25
- package/assets/guardrails/methodology/ux/accessibility-standards.md +38 -0
- package/assets/guardrails/methodology/ux/content-design.md +34 -0
- package/assets/guardrails/methodology/ux/interaction-states.md +45 -0
- package/assets/guardrails/methodology/ux/journey-mapping.md +41 -0
- package/assets/guardrails/project-specific/domain/domain-requirements.md +25 -38
- package/assets/guardrails/project-specific/domain-rule-bindings.md +33 -69
- package/assets/guardrails/project-specific/frontend/ui-requirements.md +2 -2
- package/assets/guardrails/project-specific/service/technical-requirements.md +2 -2
- package/assets/guardrails/project-specific/tech-rule-bindings.md +16 -13
- package/assets/guardrails/sample-healthcare/domain/domain-requirements.md +58 -0
- package/assets/guardrails/sample-healthcare/domain-rule-bindings.md +138 -0
- package/assets/guardrails/sample-healthcare/frontend/ui-requirements.md +43 -0
- package/assets/guardrails/sample-healthcare/service/technical-requirements.md +40 -0
- package/assets/guardrails/sample-healthcare/tech-rule-bindings.md +303 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +216 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/config.js +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/foundation.d.ts +36 -0
- package/dist/utils/foundation.d.ts.map +1 -0
- package/dist/utils/foundation.js +109 -0
- package/dist/utils/foundation.js.map +1 -0
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
# aw-spec-actions.sh <subcommand> [options]
|
|
13
13
|
#
|
|
14
14
|
# Subcommands:
|
|
15
|
-
# resolve-domain --command <name> -> {domain, rulemap_exists, candidates[]}
|
|
16
|
-
# resolve-tech --command <name> -> {domain, techrulemap_exists, candidates[]
|
|
15
|
+
# resolve-domain --command <name> -> {domain, rulemap_exists, heading_found, candidates[], missing[]}
|
|
16
|
+
# resolve-tech --command <name> -> {domain, techrulemap_exists, heading_found, candidates[], missing[],
|
|
17
|
+
# missing_elsewhere[], absent_corpora[], absent_corpora_text, absent_files,
|
|
18
|
+
# skipped[], unknown_placeholders[], declared_stacks[], stack_mismatch, mismatched_stacks[]}
|
|
17
19
|
# scaffold --feature-dir <path> [--allow-existing]
|
|
18
20
|
# -> {feature_dir, spec_file, template, created, spec_created}
|
|
19
21
|
# resolve-hooks --phase <before_specify|after_specify>
|
|
@@ -30,6 +32,9 @@
|
|
|
30
32
|
# --profile builds root_options for the stack already chosen)
|
|
31
33
|
# stack-detect [--kind <k>] [--exclude-root <p>]… -> {matches[], backend, frontend, count}
|
|
32
34
|
# (matches the repo's OWN files against the profiles; writes nothing)
|
|
35
|
+
# stack-rules --kind <k> [--declared] -> {project, map, map_exists, kind, profiles[], note,
|
|
36
|
+
# declared, lane_profile, verdict, lane_coverage, ok, warning}
|
|
37
|
+
# (which stacks the active project's technical map has rule files for; read-only)
|
|
33
38
|
# lane-declare --profile <id|label|none|detected> [--kind <k>] [--id <lane>] [--source-path|--root <p>]
|
|
34
39
|
# [--stack <label>] [--origin declared|reverse-engineered] [--require-existing-root]
|
|
35
40
|
# [--build <c>] [--test <c>] [--run <c>] [--prototype-root <p>] [--prototype-run <c>] [--no-prototype]
|
|
@@ -55,17 +60,32 @@
|
|
|
55
60
|
# lane-probe [--kind <k>] [--hint <text>] -> {declared, declared_profile, hint_profile, hint_terms[], signals[], any, evidence}
|
|
56
61
|
# (the FILE signals for "does this lane exist"; resolve-tech carries the rule signal)
|
|
57
62
|
# lane-set (--id <lane>|--kind <k>) [--set KEY=VALUE]… [--unset KEY]… -> {ok, lane_id, set, removed, written}
|
|
58
|
-
# (KEY: contract_run, contract, client; an empty VALUE changes nothing)
|
|
59
|
-
# contract-export [--timeout <s>] -> {ok, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}
|
|
60
|
-
# (
|
|
61
|
-
# contract-draft --feature-dir <p>
|
|
62
|
-
# (the feature's api-draft.json
|
|
63
|
-
#
|
|
64
|
-
#
|
|
63
|
+
# (KEY: contract_write, contract_run, contract, client; an empty VALUE changes nothing)
|
|
64
|
+
# contract-export [--timeout <s>] -> {ok, way, cause, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}
|
|
65
|
+
# (the service lane's API document as built: its contract_write command, or contract_run + contract)
|
|
66
|
+
# contract-draft --feature-dir <p> [--cr <id>] -> {ok, written, removed, path, plan, operations, ids[], ids_csv, unread[], unread_count, dropped[], dropped_text, reason}
|
|
67
|
+
# (the feature's api-draft.json from its api-plan.md — its backend plan's API section before it has one;
|
|
68
|
+
# with --cr, the change's from its delta-api-plan.md; dropped = operations the replaced draft had)
|
|
69
|
+
# contract-combine -> {ok, doc, planned, changes_csv, removed_csv, unserved_csv, unplanned_csv, clashes_csv, export, status, reason}
|
|
70
|
+
# (.appweaver/contracts/openapi.combined.json: every planned operation with the open changes applied,
|
|
71
|
+
# plus the served operations no plan describes)
|
|
65
72
|
# 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
|
|
73
|
+
# (the frontend plan's RQ-n requests and the API plan's answers)
|
|
67
74
|
# 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[],
|
|
75
|
+
# contract-check --plan <p> [--doc <d>] -> {ok, cited, ids[], missing[], removed_csv, unserved_csv, unplanned_csv, uncited, doc_exists, reason}
|
|
76
|
+
# contract-verify --feature-dir <p> [--cr <id>] [--doc <d>] [--check]
|
|
77
|
+
# -> {ok, checked, planned, missing[], moved[], renamed[], still_served[], fields[], unplanned[], findings[], issues_text, warning, note}
|
|
78
|
+
# (the as-built document vs the operations the feature's plan — or one change — names;
|
|
79
|
+
# --check reads no document -> {ok, check, mode, cr, planned, removed, changes_applied[], needed, note}: is there anything to check)
|
|
80
|
+
# contract-merge --feature-dir <p> --cr <id> [--check]
|
|
81
|
+
# -> {ok, merged, written, split, path, delta, added[], changed[], removed[], from_table[], block_kept[],
|
|
82
|
+
# id_added[], not_found[], requests[], history_row, draft, draft_operations, dropped_text, warning, reason}
|
|
83
|
+
# (finalize-delta: the change's delta-api-plan.md merged into the feature's api-plan.md, by endpoint block;
|
|
84
|
+
# --check writes nothing -> {ok, check, cr, path, delta, has_delta, merged, needed, reason}, close-delta's guard)
|
|
85
|
+
# api-split --feature-dir <p> [--check] [--cr <id>]
|
|
86
|
+
# -> {ok, split, needed, path, backend_plan, endpoints, requests_moved, draft_operations, dropped_text, reason}
|
|
87
|
+
# (a feature planned before api-plan.md: its endpoint blocks and requests table moved out of its backend plan;
|
|
88
|
+
# --check only reports `needed`, and --cr checks a change's delta backend plan the same way)
|
|
69
89
|
# contract-impact --delta-backend <p> --frontend-plan <p> [--delta-frontend <p>]
|
|
70
90
|
# -> {changes[], affected[], addressed[], unaddressed[], unaddressed_csv, warn}
|
|
71
91
|
# (operations a change's service delta changes or removes that the frontend binds to,
|
|
@@ -79,14 +99,18 @@
|
|
|
79
99
|
# version-bootstrap-done [--feature-dir <path>] -> {…, cleared} (the ONLY clearer of the --bootstrap marker; call it when the first draft is finished)
|
|
80
100
|
# feature-lanes [--feature-dir <path>] [--set backend|frontend|backend,frontend] [--clear] -> {…, lanes[]}
|
|
81
101
|
# (the declared lanes this feature has a surface in; empty = every declared lane)
|
|
82
|
-
# feature-paths -> {feature, feature_slug, feature_dir, spec_file, backend_plan, frontend_plan, *_exists,
|
|
102
|
+
# feature-paths -> {feature, feature_slug, feature_dir, spec_file, backend_plan, frontend_plan, api_plan, *_exists,
|
|
83
103
|
# design_file/ui_design_file (aliases of the two plans), spec_exists, bootstrap_pending, needs_bootstrap}
|
|
84
104
|
# checklist-read --file <path> -> {exists, total, checked, items[]}
|
|
85
105
|
# checklist-apply --file <path> --results <json>-> {exists, before_checked, after_checked, total, newly_passing[], regressions[]}
|
|
86
|
-
# plan-coverage --plan <path> --kind backend|frontend [--mode bootstrap|delta] [--
|
|
87
|
-
# [--test-path <p>] [--reported <json list>]... [--list-only]
|
|
106
|
+
# plan-coverage --plan <path> --kind backend|frontend [--mode bootstrap|delta] [--api-plan <path>]
|
|
107
|
+
# [--source-path <p>] [--test-path <p>] [--reported <json list>]... [--list-only]
|
|
88
108
|
# -> {ok, total, names[], names_csv, elements[], gaps[], gap_count, gaps_text}
|
|
89
109
|
# (which plan elements the lane's code names and the model reported done)
|
|
110
|
+
# test-citations --plan <path> --kind backend|frontend [--mode bootstrap|delta] [--paths <json list>]...
|
|
111
|
+
# [--source-path <p>] [--test-path <p>] [--spec <p>] [--model <p>] [--known-gaps <json>]...
|
|
112
|
+
# -> {ok, all_cited, clean, required[], uncited[], uncited_csv, findings[], warning}
|
|
113
|
+
# (which of the plan's FR-/BR- ids no test of the slice names)
|
|
90
114
|
# detect-project -> {scaffolded, markers[]}
|
|
91
115
|
# foundation-list [--kind <k>] [--profile <id>] [--ecosystem <e>] [--lane-id <id>] [--args '<F> <A>']
|
|
92
116
|
# -> {options[], entries[], count, foundations[], specs[], preselect_option, preselect_app, rejected_out}
|
|
@@ -119,12 +143,14 @@
|
|
|
119
143
|
# -> {ok, exists, issues, placeholders, placeholder_check, version_body, version_report, version_match, bad_dates}
|
|
120
144
|
# agent-context-update [--plan <path>] -> {ok, ran, message}
|
|
121
145
|
#
|
|
122
|
-
# plan-scaffold --feature-dir <path> [--lane backend|frontend|service|ui] [--template <name>]
|
|
123
|
-
# (alias: design-scaffold) -> {feature_dir, plan_doc, design_doc, lane, created, migrated, override_active, template, template_source}
|
|
146
|
+
# plan-scaffold --feature-dir <path> [--lane backend|frontend|api|service|ui] [--template <name>]
|
|
147
|
+
# (alias: design-scaffold) -> {feature_dir, plan_doc, design_doc, lane, created, migrated, blocked, override_active, template, template_source}
|
|
148
|
+
# (--lane api: api-plan.md; blocked while the backend plan still carries the API — run api-split)
|
|
124
149
|
# (backend|frontend write backend-plan.md / frontend-plan.md and MIGRATE a
|
|
125
150
|
# legacy design.md / design.ui.md by renaming it; service|ui keep the legacy names)
|
|
126
|
-
# delta-plan-scaffold --cr-id <id> [--lane backend|frontend] [--feature-dir <path>] [--template <name>]
|
|
127
|
-
# -> {cr_id, cr_dir, lane, delta_plan, created, migrated, override_active, template}
|
|
151
|
+
# delta-plan-scaffold --cr-id <id> [--lane backend|frontend|api] [--feature-dir <path>] [--template <name>]
|
|
152
|
+
# -> {cr_id, cr_dir, lane, delta_plan, created, migrated, blocked, override_active, template}
|
|
153
|
+
# (--lane api: delta-api-plan.md; blocked while the delta backend plan still carries the change's API)
|
|
128
154
|
# (design-extract / design-reconcile / design-paths are RETIRED — the path-tagged
|
|
129
155
|
# four-document model is gone. "Which files is this slice made of" is now an OKF
|
|
130
156
|
# query: aw-xref concept-paths / concept-upsert --merge.)
|
|
@@ -151,7 +177,7 @@
|
|
|
151
177
|
# (--lane lists only the changes touching one of those lanes; none = every open change)
|
|
152
178
|
# cr-paths --cr-id <id> [--feature-dir <path>]
|
|
153
179
|
# -> {cr_dir, delta_spec, delta_model, delta_backend_plan, delta_frontend_plan,
|
|
154
|
-
# wave, lanes[], lanes_csv, lanes_recorded, touches_backend,
|
|
180
|
+
# delta_api_plan, wave, lanes[], lanes_csv, lanes_recorded, touches_backend,
|
|
155
181
|
# touches_frontend, predecessor_files[], predecessor_specs[],
|
|
156
182
|
# predecessor_docs[], sibling_files[], domain_model}
|
|
157
183
|
# cr-overlap --cr-id <id> [--feature-dir <path>]
|
|
@@ -164,7 +190,7 @@
|
|
|
164
190
|
# lanes[], lanes_csv, backend, frontend, source, stamped}
|
|
165
191
|
# (the lanes a change touches, on its delta-spec's **Lanes** line; no line = every declared lane)
|
|
166
192
|
# 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>]
|
|
193
|
+
# [--tests-ran true|false] [--requirements-ok true|false] [--findings <json list>]...
|
|
168
194
|
# -> {file, lane, cr_id, verdict, tests_green, tests_ran,
|
|
169
195
|
# requirements_ok, findings, high_findings, gated}
|
|
170
196
|
# (one lane's analyze-slice-delta result, as changes/<id>/.aw-slice-gate.<lane>.json;
|
|
@@ -179,6 +205,11 @@
|
|
|
179
205
|
# context-version-bump [--feature-dir <path>] [--cr-id <id>] [--touched-model true|false]
|
|
180
206
|
# -> {ok, bumped, previous, version, model,
|
|
181
207
|
# model_exists, context, reason}
|
|
208
|
+
# model-guard-snapshot --model <path> -> {ok, model, before, exists, aggregates[], rules, cross_rules, guarded}
|
|
209
|
+
# model-guard-check --model <path> -> {ok, checked, skipped, model, rejected, restored, losses[],
|
|
210
|
+
# loss_count, losses_text, reason}
|
|
211
|
+
# (bracket a whole-file rewrite of domain-model.md; on a loss the check
|
|
212
|
+
# restores the model and keeps the rewrite as domain-model.rejected.md)
|
|
182
213
|
#
|
|
183
214
|
# All subcommands accept --help.
|
|
184
215
|
|
|
@@ -228,22 +259,34 @@ cmd_resolve_domain() {
|
|
|
228
259
|
[ -n "$domain" ] && [ -f "$rulemap" ] && rulemap_exists=true
|
|
229
260
|
|
|
230
261
|
# Collect bullet paths under "## /<command>" in the domain's domain-rule-bindings.md.
|
|
231
|
-
|
|
262
|
+
# A mapped file that is not on disk goes to `missing`, never to `candidates`, so a
|
|
263
|
+
# caller can say so instead of loading nothing in its place without a word.
|
|
264
|
+
# heading_found tells a heading that is deliberately empty (a `_None_` line) apart
|
|
265
|
+
# from one that is absent or misspelled — both give no candidates.
|
|
266
|
+
local -a candidates=() missing=()
|
|
267
|
+
local heading_found=false
|
|
232
268
|
if [ "$rulemap_exists" = true ]; then
|
|
233
|
-
|
|
269
|
+
grep -qxF "## /$command_name" "$rulemap" && heading_found=true
|
|
270
|
+
# awk: capture "- path" bullets between the target "## <heading>" and the next "## ".
|
|
271
|
+
# A path written as code (- `path`) is read without its backticks.
|
|
234
272
|
while IFS= read -r line; do
|
|
235
|
-
[ -n "$line" ]
|
|
273
|
+
[ -n "$line" ] || continue
|
|
274
|
+
if [ -f "$repo_root/$line" ]; then candidates+=("$line"); else missing+=("$line"); fi
|
|
236
275
|
done < <(awk -v h="## /$command_name" '
|
|
237
276
|
$0==h {grab=1; next}
|
|
238
277
|
/^## / {grab=0}
|
|
239
278
|
grab && /^[[:space:]]*-[[:space:]]+/ {
|
|
240
|
-
sub(/^[[:space:]]*-[[:space:]]+/,""); sub(/[[:space:]]+$/,"")
|
|
279
|
+
sub(/^[[:space:]]*-[[:space:]]+/,""); sub(/[[:space:]]+$/,"")
|
|
280
|
+
if ($0 ~ /^`[^`]+`$/) $0 = substr($0, 2, length($0) - 2)
|
|
281
|
+
print
|
|
241
282
|
}
|
|
242
283
|
' "$rulemap")
|
|
243
284
|
fi
|
|
244
285
|
|
|
245
|
-
printf '{"domain":"%s","rulemap_exists":%s,"candidates":%s}\n' \
|
|
246
|
-
"$(json_escape "$domain")" "$rulemap_exists" "$
|
|
286
|
+
printf '{"domain":"%s","rulemap_exists":%s,"heading_found":%s,"candidates":%s,"missing":%s}\n' \
|
|
287
|
+
"$(json_escape "$domain")" "$rulemap_exists" "$heading_found" \
|
|
288
|
+
"$(json_str_array "${candidates[@]+"${candidates[@]}"}")" \
|
|
289
|
+
"$(json_str_array "${missing[@]+"${missing[@]}"}")"
|
|
247
290
|
}
|
|
248
291
|
|
|
249
292
|
# ──────────────────────────────── resolve-tech ─────────────────────────────
|
|
@@ -332,28 +375,39 @@ def expand(line):
|
|
|
332
375
|
return HOLE.sub(lambda m: subs[m.group(1)], line), ""
|
|
333
376
|
|
|
334
377
|
bullets = []
|
|
378
|
+
# Whether the command's heading is in the map at all. A heading with no bullets (a
|
|
379
|
+
# `_None_` line) is a deliberate choice; an absent or misspelled one loads the same
|
|
380
|
+
# nothing, and only this tells the caller which of the two it is.
|
|
381
|
+
heading_found = False
|
|
335
382
|
if exists:
|
|
336
383
|
text = open(techmap, encoding="utf-8").read().splitlines()
|
|
337
384
|
for heading in ("## /" + command, "## /" + command.replace("decl-", "")):
|
|
338
385
|
found, grab = [], False
|
|
339
386
|
for line in text:
|
|
340
387
|
if line.strip() == heading:
|
|
341
|
-
grab = True; continue
|
|
388
|
+
grab = True; heading_found = True; continue
|
|
342
389
|
if line.startswith("## "):
|
|
343
390
|
grab = False; continue
|
|
344
391
|
if grab:
|
|
345
392
|
m = re.match(r"^\s*-\s+(.*?)\s*$", line)
|
|
346
393
|
if m:
|
|
347
|
-
|
|
394
|
+
# A path written as code (- `path`) is read without its backticks.
|
|
395
|
+
found.append(re.sub(r"^`([^`]+)`$", r"\1", m.group(1)))
|
|
348
396
|
if found:
|
|
349
397
|
bullets = found
|
|
350
398
|
break
|
|
351
399
|
|
|
352
400
|
candidates, missing, skipped, mismatched = [], [], [], []
|
|
401
|
+
# A placeholder the kit does not know is a typo in the map, unlike one for a lane
|
|
402
|
+
# this repo does not declare, which is skipped because its rules do not apply.
|
|
403
|
+
unknown = []
|
|
353
404
|
for b in bullets:
|
|
354
405
|
path, why = expand(b)
|
|
355
406
|
if why:
|
|
356
|
-
skipped.append({"bullet": b, "why": why})
|
|
407
|
+
skipped.append({"bullet": b, "why": why})
|
|
408
|
+
if why.startswith("unknown placeholder"):
|
|
409
|
+
unknown.append(b)
|
|
410
|
+
continue
|
|
357
411
|
(candidates if os.path.isfile(os.path.join(repo, path)) else missing).append(path)
|
|
358
412
|
# A LITERAL stack directory that is not one this repo declares. Reported, never
|
|
359
413
|
# dropped: the file exists and the project chose it, so it may be a deliberate
|
|
@@ -364,12 +418,32 @@ for b in bullets:
|
|
|
364
418
|
if m.group(1) not in mismatched:
|
|
365
419
|
mismatched.append(m.group(1))
|
|
366
420
|
|
|
421
|
+
# A STACK WITH NO RULE FOLDER AT ALL. Every file the map names under it is missing, and
|
|
422
|
+
# that is a supported setup rather than a typo (STACK-PROFILES.md §7), so those files are
|
|
423
|
+
# reported as the folder they belong to. A file missing from a folder that DOES exist is
|
|
424
|
+
# a gap to fix, and stays listed one by one in missing_elsewhere. `missing` keeps both.
|
|
425
|
+
absent, absent_files, elsewhere = [], 0, []
|
|
426
|
+
for p in missing:
|
|
427
|
+
m = re.match(r"^(guardrails/technology/[^/]+/)", p)
|
|
428
|
+
if m and not os.path.isdir(os.path.join(repo, m.group(1))):
|
|
429
|
+
if m.group(1) not in absent:
|
|
430
|
+
absent.append(m.group(1))
|
|
431
|
+
absent_files += 1
|
|
432
|
+
else:
|
|
433
|
+
elsewhere.append(p)
|
|
434
|
+
|
|
367
435
|
print(json.dumps({
|
|
368
436
|
"domain": domain,
|
|
369
437
|
"techrulemap_exists": exists,
|
|
438
|
+
"heading_found": heading_found,
|
|
370
439
|
"candidates": candidates,
|
|
371
440
|
"missing": missing,
|
|
441
|
+
"missing_elsewhere": elsewhere,
|
|
442
|
+
"absent_corpora": absent,
|
|
443
|
+
"absent_corpora_text": " and ".join(absent),
|
|
444
|
+
"absent_files": absent_files,
|
|
372
445
|
"skipped": skipped,
|
|
446
|
+
"unknown_placeholders": unknown,
|
|
373
447
|
"declared_stacks": sorted(declared),
|
|
374
448
|
"stack_mismatch": bool(mismatched),
|
|
375
449
|
"mismatched_stacks": mismatched,
|
|
@@ -842,6 +916,22 @@ cmd_stack_detect() {
|
|
|
842
916
|
_aw_stack_py detect "${fwd[@]}"
|
|
843
917
|
}
|
|
844
918
|
|
|
919
|
+
# Which stacks the active project's technical map has rule files for (aw-stack.py rules).
|
|
920
|
+
# A stack without them is a supported setup; select-stack reports it in the menu and
|
|
921
|
+
# warns for a declared lane, so the operator learns it where the stack is chosen.
|
|
922
|
+
cmd_stack_rules() {
|
|
923
|
+
local -a fwd=()
|
|
924
|
+
while [ $# -gt 0 ]; do
|
|
925
|
+
case "$1" in
|
|
926
|
+
--kind) fwd+=("--kind" "$2"); shift 2 ;;
|
|
927
|
+
--declared) fwd+=("--declared"); shift ;;
|
|
928
|
+
--help|-h) echo "stack-rules --kind backend|frontend [--declared] -> {project, map, map_exists, kind, profiles[], note, declared, lane_profile, verdict, lane_coverage, ok, warning}"; return 0 ;;
|
|
929
|
+
*) echo "stack-rules: unknown arg '$1'" >&2; return 1 ;;
|
|
930
|
+
esac
|
|
931
|
+
done
|
|
932
|
+
_aw_stack_py rules "${fwd[@]}"
|
|
933
|
+
}
|
|
934
|
+
|
|
845
935
|
cmd_lane_declare() {
|
|
846
936
|
local -a fwd=()
|
|
847
937
|
while [ $# -gt 0 ]; do
|
|
@@ -875,45 +965,47 @@ cmd_lane_resolve() {
|
|
|
875
965
|
_aw_stack_py resolve "${fwd[@]}"
|
|
876
966
|
}
|
|
877
967
|
|
|
878
|
-
# Change the API-contract keys (contract_run, contract, client) on a lane
|
|
879
|
-
# declared, leaving the rest of it alone. `--set KEY=` with an empty value changes
|
|
880
|
-
# nothing; `--unset KEY` removes one. Callers: foundation (a pack's
|
|
881
|
-
# implement (the client command a developer supplies).
|
|
968
|
+
# Change the API-contract keys (contract_write, contract_run, contract, client) on a lane
|
|
969
|
+
# already declared, leaving the rest of it alone. `--set KEY=` with an empty value changes
|
|
970
|
+
# nothing; `--unset KEY` removes one. Callers: foundation (a pack's or a spec's lane
|
|
971
|
+
# settings) and implement (the client command a developer supplies).
|
|
882
972
|
cmd_lane_set() {
|
|
883
973
|
local -a fwd=()
|
|
884
974
|
while [ $# -gt 0 ]; do
|
|
885
975
|
case "$1" in
|
|
886
976
|
--id|--kind|--set|--unset) fwd+=("$1" "$2"); shift 2 ;;
|
|
887
|
-
--help|-h) echo "lane-set (--id <lane>|--kind backend|frontend) [--set KEY=VALUE]… [--unset KEY]… (KEY: contract_run, contract, client)"; return 0 ;;
|
|
977
|
+
--help|-h) echo "lane-set (--id <lane>|--kind backend|frontend) [--set KEY=VALUE]… [--unset KEY]… (KEY: contract_write, contract_run, contract, client)"; return 0 ;;
|
|
888
978
|
*) echo "lane-set: unknown arg '$1'" >&2; return 1 ;;
|
|
889
979
|
esac
|
|
890
980
|
done
|
|
891
981
|
_aw_stack_py set "${fwd[@]}"
|
|
892
982
|
}
|
|
893
983
|
|
|
894
|
-
#
|
|
895
|
-
# The API contract between the lanes:
|
|
896
|
-
#
|
|
897
|
-
#
|
|
898
|
-
#
|
|
984
|
+
# ─────── contract-export / -draft / -combine / -requests / -client / -check / -verify / -merge, api-split ───────
|
|
985
|
+
# The API contract between the lanes: the API plans' drafts are the contract, combined for
|
|
986
|
+
# the frontend with the open changes applied, and each change's delta API plan is merged
|
|
987
|
+
# into its feature's API plan when the change is finalized; the request ledger runs between
|
|
988
|
+
# the plans; the frontend lane's client is generated from the combined document, and the
|
|
989
|
+
# operation ids its plan cites are checked against it. The service lane's document as
|
|
990
|
+
# built is written by its own way and checked against the plan. All delegate to aw-stack.py.
|
|
899
991
|
cmd_contract_export() {
|
|
900
992
|
local -a fwd=()
|
|
901
993
|
while [ $# -gt 0 ]; do
|
|
902
994
|
case "$1" in
|
|
903
995
|
--timeout) fwd+=("$1" "$2"); shift 2 ;;
|
|
904
|
-
--help|-h) echo "contract-export [--timeout <seconds>] -> {ok, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}"; return 0 ;;
|
|
996
|
+
--help|-h) echo "contract-export [--timeout <seconds>] -> {ok, way, cause, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}"; return 0 ;;
|
|
905
997
|
*) echo "contract-export: unknown arg '$1'" >&2; return 1 ;;
|
|
906
998
|
esac
|
|
907
999
|
done
|
|
908
|
-
_aw_stack_py contract-export "${fwd[@]}"
|
|
1000
|
+
_aw_stack_py contract-export "${fwd[@]+"${fwd[@]}"}"
|
|
909
1001
|
}
|
|
910
1002
|
|
|
911
1003
|
cmd_contract_draft() {
|
|
912
1004
|
local -a fwd=()
|
|
913
1005
|
while [ $# -gt 0 ]; do
|
|
914
1006
|
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 ;;
|
|
1007
|
+
--feature-dir|--cr) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
1008
|
+
--help|-h) echo "contract-draft --feature-dir <path> [--cr <id>] -> {ok, written, removed, path, plan, operations, ids, ids_csv, unread, unread_count, dropped, dropped_text, reason}"; return 0 ;;
|
|
917
1009
|
*) echo "contract-draft: unknown arg '$1'" >&2; return 1 ;;
|
|
918
1010
|
esac
|
|
919
1011
|
done
|
|
@@ -923,7 +1015,7 @@ cmd_contract_draft() {
|
|
|
923
1015
|
cmd_contract_combine() {
|
|
924
1016
|
while [ $# -gt 0 ]; do
|
|
925
1017
|
case "$1" in
|
|
926
|
-
--help|-h) echo "contract-combine -> {ok, doc,
|
|
1018
|
+
--help|-h) echo "contract-combine -> {ok, doc, planned, changes_csv, removed_csv, unserved_csv, unplanned_csv, clashes_csv, export, export_exists, status, reason}"; return 0 ;;
|
|
927
1019
|
*) echo "contract-combine: unknown arg '$1'" >&2; return 1 ;;
|
|
928
1020
|
esac
|
|
929
1021
|
done
|
|
@@ -962,13 +1054,58 @@ cmd_contract_check() {
|
|
|
962
1054
|
while [ $# -gt 0 ]; do
|
|
963
1055
|
case "$1" in
|
|
964
1056
|
--plan|--doc) fwd+=("$1" "$2"); shift 2 ;;
|
|
965
|
-
--help|-h) echo "contract-check --plan <frontend plan> [--doc <api document>] -> {ok, cited, ids, missing, uncited, doc_exists, reason}"; return 0 ;;
|
|
1057
|
+
--help|-h) echo "contract-check --plan <frontend plan> [--doc <api document>] -> {ok, cited, ids, missing, removed_csv, unserved_csv, unplanned_csv, uncited, doc_exists, reason}"; return 0 ;;
|
|
966
1058
|
*) echo "contract-check: unknown arg '$1'" >&2; return 1 ;;
|
|
967
1059
|
esac
|
|
968
1060
|
done
|
|
969
1061
|
_aw_stack_py contract-check "${fwd[@]}"
|
|
970
1062
|
}
|
|
971
1063
|
|
|
1064
|
+
# Does the built service serve what its plan names — the feature's operations, or with --cr
|
|
1065
|
+
# one change's; with --check, only whether the plan names anything to check. Each value is
|
|
1066
|
+
# forwarded only when it is not empty.
|
|
1067
|
+
cmd_contract_verify() {
|
|
1068
|
+
local -a fwd=()
|
|
1069
|
+
while [ $# -gt 0 ]; do
|
|
1070
|
+
case "$1" in
|
|
1071
|
+
--feature-dir|--cr|--doc) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
1072
|
+
--check) fwd+=("$1"); shift ;;
|
|
1073
|
+
--help|-h) echo "contract-verify --feature-dir <path> [--cr <id>] [--doc <as-built document>] [--check] -> {ok, checked, planned, missing, moved, renamed, still_served, fields, unplanned, findings, issues_text, warning, note}; --check reads no document -> {ok, check, mode, cr, planned, removed, changes_applied, needed, note}"; return 0 ;;
|
|
1074
|
+
*) echo "contract-verify: unknown arg '$1'" >&2; return 1 ;;
|
|
1075
|
+
esac
|
|
1076
|
+
done
|
|
1077
|
+
_aw_stack_py contract-verify "${fwd[@]+"${fwd[@]}"}"
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
# A change's delta API plan, merged into its feature's API plan (finalize-delta); with
|
|
1081
|
+
# --check, only whether it is still to merge (close-delta).
|
|
1082
|
+
cmd_contract_merge() {
|
|
1083
|
+
local -a fwd=()
|
|
1084
|
+
while [ $# -gt 0 ]; do
|
|
1085
|
+
case "$1" in
|
|
1086
|
+
--feature-dir|--cr) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
1087
|
+
--check) fwd+=("$1"); shift ;;
|
|
1088
|
+
--help|-h) echo "contract-merge --feature-dir <path> --cr <id> [--check] -> {ok, merged, written, split, path, delta, added, changed, removed, from_table, block_kept, id_added, not_found, requests, history_row, draft, draft_operations, dropped_text, warning, reason}; --check writes nothing -> {ok, check, cr, path, delta, has_delta, merged, needed, reason}"; return 0 ;;
|
|
1089
|
+
*) echo "contract-merge: unknown arg '$1'" >&2; return 1 ;;
|
|
1090
|
+
esac
|
|
1091
|
+
done
|
|
1092
|
+
_aw_stack_py contract-merge "${fwd[@]+"${fwd[@]}"}"
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
# A feature's endpoints and requests table, moved out of its backend plan into an API plan.
|
|
1096
|
+
cmd_api_split() {
|
|
1097
|
+
local -a fwd=()
|
|
1098
|
+
while [ $# -gt 0 ]; do
|
|
1099
|
+
case "$1" in
|
|
1100
|
+
--feature-dir|--cr) if [ -n "$2" ]; then fwd+=("$1" "$2"); fi; shift 2 ;;
|
|
1101
|
+
--check) fwd+=("$1"); shift ;;
|
|
1102
|
+
--help|-h) echo "api-split --feature-dir <path> [--check] [--cr <id>] -> {ok, split, needed, path, backend_plan, endpoints, requests_moved, draft_operations, dropped_text, reason}"; return 0 ;;
|
|
1103
|
+
*) echo "api-split: unknown arg '$1'" >&2; return 1 ;;
|
|
1104
|
+
esac
|
|
1105
|
+
done
|
|
1106
|
+
_aw_stack_py api-split "${fwd[@]+"${fwd[@]}"}"
|
|
1107
|
+
}
|
|
1108
|
+
|
|
972
1109
|
# What a change's service delta changes or removes that the frontend master plan binds to,
|
|
973
1110
|
# and whether the change's frontend delta plan carries it. Each path is forwarded only when
|
|
974
1111
|
# it is not empty.
|
|
@@ -1434,6 +1571,39 @@ cmd_model_scaffold() {
|
|
|
1434
1571
|
"$(json_escape "$tpl")" "$created"
|
|
1435
1572
|
}
|
|
1436
1573
|
|
|
1574
|
+
# ──────────────────── model-guard-snapshot / model-guard-check ─────────────────────
|
|
1575
|
+
# specify (bootstrap) and adopt write the context's WHOLE domain-model.md, and from a
|
|
1576
|
+
# context's second feature on that file holds the aggregates, BR-/XBR- rules and ledger rows
|
|
1577
|
+
# other features rely on. These two bracket that step: the snapshot copies the model aside,
|
|
1578
|
+
# the check compares the rewrite with the copy and, on a loss, puts the model back and keeps
|
|
1579
|
+
# the rewrite as domain-model.rejected.md. THE RULE LIVES IN python/aw-model-guard.py, which
|
|
1580
|
+
# both shells invoke — these are thin wrappers that resolve the path and hand over. Both
|
|
1581
|
+
# exit 0 with `ok` in the JSON; the workflow's gate owns the stop.
|
|
1582
|
+
cmd_model_guard() {
|
|
1583
|
+
local op="$1"; shift
|
|
1584
|
+
local model=""
|
|
1585
|
+
while [ $# -gt 0 ]; do
|
|
1586
|
+
case "$1" in
|
|
1587
|
+
--model) model="$2"; shift 2 ;;
|
|
1588
|
+
--help|-h)
|
|
1589
|
+
if [ "$op" = "snapshot" ]; then
|
|
1590
|
+
echo "model-guard-snapshot --model <path> -> {ok, model, before, exists, aggregates[], rules, cross_rules, guarded}"
|
|
1591
|
+
else
|
|
1592
|
+
echo "model-guard-check --model <path> -> {ok, checked, skipped, model, rejected, restored, losses[], loss_count, losses_text, reason}"
|
|
1593
|
+
fi
|
|
1594
|
+
return 0 ;;
|
|
1595
|
+
*) echo "model-guard-$op: unknown arg '$1'" >&2; return 1 ;;
|
|
1596
|
+
esac
|
|
1597
|
+
done
|
|
1598
|
+
[ -n "$model" ] || { echo "model-guard-$op: --model required" >&2; return 1; }
|
|
1599
|
+
local repo_root; repo_root=$(get_repo_root)
|
|
1600
|
+
[[ "$model" != /* ]] && model="$repo_root/$model"
|
|
1601
|
+
local core="$SCRIPT_DIR/../python/aw-model-guard.py"
|
|
1602
|
+
if [ ! -f "$core" ]; then echo "model-guard-$op: core not found at $core" >&2; return 1; fi
|
|
1603
|
+
if ! command -v python3 >/dev/null 2>&1; then echo "model-guard-$op: python3 is required" >&2; return 1; fi
|
|
1604
|
+
python3 "$core" "$op" --model "$model" --root "$repo_root"
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1437
1607
|
# ─────────────────────────────── list-features ─────────────────────────────
|
|
1438
1608
|
# Enumerate the aggregates a user can pick as the active feature. Two sources,
|
|
1439
1609
|
# MERGED (deduped by feature name, case-insensitive):
|
|
@@ -2015,6 +2185,11 @@ cmd_feature_paths() {
|
|
|
2015
2185
|
local spec="$fd/spec.md" design="$fd/backend-plan.md" ui_design="$fd/frontend-plan.md"
|
|
2016
2186
|
[ ! -f "$design" ] && [ -f "$fd/design.md" ] && design="$fd/design.md"
|
|
2017
2187
|
[ ! -f "$ui_design" ] && [ -f "$fd/design.ui.md" ] && ui_design="$fd/design.ui.md"
|
|
2188
|
+
# The API plan beside the backend plan: the service's published endpoints, a master that
|
|
2189
|
+
# finalize-delta merges each change's API into. A feature planned before it existed has
|
|
2190
|
+
# none, and keeps its endpoints in its backend plan until `api-split` moves them.
|
|
2191
|
+
local api_plan="$fd/api-plan.md" api_plan_exists=false
|
|
2192
|
+
[ -f "$api_plan" ] && api_plan_exists=true
|
|
2018
2193
|
# needs_bootstrap, not spec_exists, is what picks specify's branch. A crashed first run
|
|
2019
2194
|
# leaves spec.md on disk with the marker still up, and branching on the file alone read
|
|
2020
2195
|
# that as a spec to EXTEND — re-applying the same input over an unfinished v1.0.
|
|
@@ -2026,9 +2201,10 @@ cmd_feature_paths() {
|
|
|
2026
2201
|
local needs_bootstrap=false
|
|
2027
2202
|
[ "$spec_exists" = false ] && needs_bootstrap=true
|
|
2028
2203
|
[ "$bootstrap_pending" = true ] && needs_bootstrap=true
|
|
2029
|
-
printf '{"feature":"%s","feature_slug":"%s","feature_dir":"%s","spec_file":"%s","backend_plan":"%s","frontend_plan":"%s","backend_plan_exists":%s,"frontend_plan_exists":%s,"design_file":"%s","ui_design_file":"%s","spec_exists":%s,"design_exists":%s,"ui_design_exists":%s,"bootstrap_pending":%s,"needs_bootstrap":%s}\n' \
|
|
2204
|
+
printf '{"feature":"%s","feature_slug":"%s","feature_dir":"%s","spec_file":"%s","backend_plan":"%s","frontend_plan":"%s","backend_plan_exists":%s,"frontend_plan_exists":%s,"api_plan":"%s","api_plan_exists":%s,"design_file":"%s","ui_design_file":"%s","spec_exists":%s,"design_exists":%s,"ui_design_exists":%s,"bootstrap_pending":%s,"needs_bootstrap":%s}\n' \
|
|
2030
2205
|
"$(json_escape "$feature")" "$(json_escape "$feature_slug")" "$(json_escape "$fd")" "$(json_escape "$spec")" \
|
|
2031
2206
|
"$(json_escape "$design")" "$(json_escape "$ui_design")" "$design_exists" "$ui_design_exists" \
|
|
2207
|
+
"$(json_escape "$api_plan")" "$api_plan_exists" \
|
|
2032
2208
|
"$(json_escape "$design")" "$(json_escape "$ui_design")" "$spec_exists" "$design_exists" "$ui_design_exists" \
|
|
2033
2209
|
"$bootstrap_pending" "$needs_bootstrap"
|
|
2034
2210
|
}
|
|
@@ -3251,7 +3427,7 @@ cmd_design_scaffold() {
|
|
|
3251
3427
|
# Priority-1), so the defaults already yield the as-built structure for
|
|
3252
3428
|
# forward `plan` and adopt alike, and this is rarely needed.
|
|
3253
3429
|
--template) template_arg="$2"; shift 2 ;;
|
|
3254
|
-
--help|-h) echo "plan-scaffold --feature-dir <path> [--lane backend|frontend|service|ui] [--template <name>]"; return 0 ;;
|
|
3430
|
+
--help|-h) echo "plan-scaffold --feature-dir <path> [--lane backend|frontend|api|service|ui] [--template <name>]"; return 0 ;;
|
|
3255
3431
|
*) echo "design-scaffold: unknown arg '$1'" >&2; return 1 ;;
|
|
3256
3432
|
esac
|
|
3257
3433
|
done
|
|
@@ -3259,14 +3435,16 @@ cmd_design_scaffold() {
|
|
|
3259
3435
|
# names, and a MIGRATION — a legacy design.md / design.ui.md is RENAMED into the plan
|
|
3260
3436
|
# name, so a feature never carries two spellings of one master and the rename costs
|
|
3261
3437
|
# no flag day. service / ui stay byte-identical for the callers that still write the
|
|
3262
|
-
# legacy names (adopt's reverse pass).
|
|
3438
|
+
# legacy names (adopt's reverse pass). api is the backend lane's API plan, beside its
|
|
3439
|
+
# plan — one template for every project, since its endpoint block form is fixed.
|
|
3263
3440
|
local doc_name legacy_name=""
|
|
3264
3441
|
case "$lane_arg" in
|
|
3265
3442
|
service) doc_name="design.md"; [ -n "$template_arg" ] || template_arg="design-template" ;;
|
|
3266
3443
|
ui) doc_name="design.ui.md"; [ -n "$template_arg" ] || template_arg="ui-design-template" ;;
|
|
3267
3444
|
backend) doc_name="backend-plan.md"; legacy_name="design.md"; [ -n "$template_arg" ] || template_arg="design-template" ;;
|
|
3268
3445
|
frontend) doc_name="frontend-plan.md"; legacy_name="design.ui.md"; [ -n "$template_arg" ] || template_arg="ui-design-template" ;;
|
|
3269
|
-
|
|
3446
|
+
api) doc_name="api-plan.md"; [ -n "$template_arg" ] || template_arg="api-plan-template" ;;
|
|
3447
|
+
*) echo "design-scaffold: unknown --lane '$lane_arg' (backend|frontend|api|service|ui)" >&2; return 1 ;;
|
|
3270
3448
|
esac
|
|
3271
3449
|
local repo_root; repo_root=$(get_repo_root)
|
|
3272
3450
|
local fd="$feature_dir_arg"
|
|
@@ -3282,7 +3460,17 @@ cmd_design_scaffold() {
|
|
|
3282
3460
|
if [ ! -f "$target" ] && [ -n "$legacy_name" ] && [ -f "$fd/$legacy_name" ]; then
|
|
3283
3461
|
mv "$fd/$legacy_name" "$target"; migrated="true"
|
|
3284
3462
|
fi
|
|
3285
|
-
|
|
3463
|
+
# An API plan is never scaffolded beside a backend plan that still carries the API: the
|
|
3464
|
+
# empty template would be read in its place and the draft would lose every endpoint.
|
|
3465
|
+
# `api-split` moves them first.
|
|
3466
|
+
local blocked="false"
|
|
3467
|
+
if [ "$lane_arg" = "api" ] && [ ! -f "$target" ]; then
|
|
3468
|
+
blocked=$(_aw_stack_py api-split --feature-dir "$fd" --check 2>/dev/null \
|
|
3469
|
+
| python3 -c 'import json,sys
|
|
3470
|
+
try: print("true" if json.load(sys.stdin).get("needed") else "false")
|
|
3471
|
+
except Exception: print("false")' 2>/dev/null) || blocked="false"
|
|
3472
|
+
fi
|
|
3473
|
+
if [ -f "$target" ] || [ "$blocked" = "true" ]; then
|
|
3286
3474
|
created="false"
|
|
3287
3475
|
else
|
|
3288
3476
|
tpl=$(resolve_template "$template_arg" "$repo_root") || true
|
|
@@ -3298,9 +3486,9 @@ cmd_design_scaffold() {
|
|
|
3298
3486
|
local override_active="false"
|
|
3299
3487
|
[ -f "$repo_root/.appweaver/templates/overrides/${template_arg}.md" ] && override_active="true"
|
|
3300
3488
|
local template_source; template_source=$(resolve_template "$template_arg" "$repo_root") || true
|
|
3301
|
-
printf '{"feature_dir":"%s","design_doc":"%s","plan_doc":"%s","lane":"%s","created":%s,"migrated":%s,"override_active":%s,"template":"%s","template_source":"%s"}\n' \
|
|
3489
|
+
printf '{"feature_dir":"%s","design_doc":"%s","plan_doc":"%s","lane":"%s","created":%s,"migrated":%s,"blocked":%s,"override_active":%s,"template":"%s","template_source":"%s"}\n' \
|
|
3302
3490
|
"$(json_escape "$fd")" "$(json_escape "$target")" "$(json_escape "$target")" "$(json_escape "$lane_arg")" \
|
|
3303
|
-
"$created" "$migrated" "$override_active" \
|
|
3491
|
+
"$created" "$migrated" "$blocked" "$override_active" \
|
|
3304
3492
|
"$(json_escape "$template_arg")" "$(json_escape "${template_source:-}")"
|
|
3305
3493
|
}
|
|
3306
3494
|
|
|
@@ -3324,7 +3512,7 @@ cmd_delta_plan_scaffold() {
|
|
|
3324
3512
|
--feature-dir) feature_dir_arg="$2"; shift 2 ;;
|
|
3325
3513
|
--lane) lane_arg="$2"; shift 2 ;;
|
|
3326
3514
|
--template) template_arg="$2"; shift 2 ;;
|
|
3327
|
-
--help|-h) echo "delta-plan-scaffold --cr-id <id> [--lane backend|frontend] [--feature-dir <path>] [--template <name>]"; return 0 ;;
|
|
3515
|
+
--help|-h) echo "delta-plan-scaffold --cr-id <id> [--lane backend|frontend|api] [--feature-dir <path>] [--template <name>]"; return 0 ;;
|
|
3328
3516
|
*) echo "delta-plan-scaffold: unknown arg '$1'" >&2; return 1 ;;
|
|
3329
3517
|
esac
|
|
3330
3518
|
done
|
|
@@ -3336,7 +3524,9 @@ cmd_delta_plan_scaffold() {
|
|
|
3336
3524
|
[ -n "$template_arg" ] || template_arg="delta-design-template" ;;
|
|
3337
3525
|
frontend) doc_name="delta-frontend-plan.md"; legacy_name="delta-design.ui.md"
|
|
3338
3526
|
[ -n "$template_arg" ] || template_arg="ui-delta-design-template" ;;
|
|
3339
|
-
|
|
3527
|
+
api) doc_name="delta-api-plan.md"
|
|
3528
|
+
[ -n "$template_arg" ] || template_arg="delta-api-plan-template" ;;
|
|
3529
|
+
*) echo "delta-plan-scaffold: unknown --lane '$lane_arg' (backend|frontend|api)" >&2; return 1 ;;
|
|
3340
3530
|
esac
|
|
3341
3531
|
local repo_root; repo_root=$(get_repo_root)
|
|
3342
3532
|
local fd; fd=$(_resolve_feature_dir "$feature_dir_arg" "$repo_root") || { echo "delta-plan-scaffold: no feature set and no --feature-dir" >&2; return 1; }
|
|
@@ -3344,10 +3534,19 @@ cmd_delta_plan_scaffold() {
|
|
|
3344
3534
|
[ -d "$cr_dir" ] || { echo "delta-plan-scaffold: no such open change '$cr_id' (looked in $cr_dir)" >&2; return 1; }
|
|
3345
3535
|
|
|
3346
3536
|
local target="$cr_dir/$doc_name" created="false" migrated="false" tpl
|
|
3347
|
-
if [ ! -f "$target" ] && [ -f "$cr_dir/$legacy_name" ]; then
|
|
3537
|
+
if [ ! -f "$target" ] && [ -n "$legacy_name" ] && [ -f "$cr_dir/$legacy_name" ]; then
|
|
3348
3538
|
mv "$cr_dir/$legacy_name" "$target"; migrated="true"
|
|
3349
3539
|
fi
|
|
3350
|
-
|
|
3540
|
+
# A change planned before the delta API plan existed keeps its API in its delta backend
|
|
3541
|
+
# plan, which is read in its place; an empty delta API plan beside it would hide it.
|
|
3542
|
+
local blocked="false"
|
|
3543
|
+
if [ "$lane_arg" = "api" ] && [ ! -f "$target" ]; then
|
|
3544
|
+
blocked=$(_aw_stack_py api-split --feature-dir "$fd" --cr "$cr_id" 2>/dev/null \
|
|
3545
|
+
| python3 -c 'import json,sys
|
|
3546
|
+
try: print("true" if json.load(sys.stdin).get("needed") else "false")
|
|
3547
|
+
except Exception: print("false")' 2>/dev/null) || blocked="false"
|
|
3548
|
+
fi
|
|
3549
|
+
if [ ! -f "$target" ] && [ "$blocked" != "true" ]; then
|
|
3351
3550
|
tpl=$(resolve_template "$template_arg" "$repo_root") || true
|
|
3352
3551
|
if [ -n "$tpl" ] && [ -f "$tpl" ]; then cp "$tpl" "$target"; else touch "$target"; fi
|
|
3353
3552
|
created="true"
|
|
@@ -3357,15 +3556,15 @@ cmd_delta_plan_scaffold() {
|
|
|
3357
3556
|
# readable wave the template's `[N]` is left standing, which is what the merge and recall
|
|
3358
3557
|
# fences read as "not ordered yet" and refuse on, with the command that stamps it.
|
|
3359
3558
|
local scaffold_wave; scaffold_wave=$(_wave_of_readable "$fd" "$cr_id" "$repo_root")
|
|
3360
|
-
if [ -n "$scaffold_wave" ]; then
|
|
3559
|
+
if [ -n "$scaffold_wave" ] && [ -f "$target" ]; then
|
|
3361
3560
|
_stamp_header "$target" "Wave" "$scaffold_wave"
|
|
3362
3561
|
fi
|
|
3363
3562
|
local override_active="false"
|
|
3364
3563
|
[ -f "$repo_root/.appweaver/templates/overrides/${template_arg}.md" ] && override_active="true"
|
|
3365
3564
|
local template_source; template_source=$(resolve_template "$template_arg" "$repo_root") || true
|
|
3366
|
-
printf '{"cr_id":"%s","cr_dir":"%s","lane":"%s","delta_plan":"%s","created":%s,"migrated":%s,"override_active":%s,"template":"%s","template_source":"%s"}\n' \
|
|
3565
|
+
printf '{"cr_id":"%s","cr_dir":"%s","lane":"%s","delta_plan":"%s","created":%s,"migrated":%s,"blocked":%s,"override_active":%s,"template":"%s","template_source":"%s"}\n' \
|
|
3367
3566
|
"$(json_escape "$cr_id")" "$(json_escape "$cr_dir")" "$(json_escape "$lane_arg")" "$(json_escape "$target")" \
|
|
3368
|
-
"$created" "$migrated" "$override_active" \
|
|
3567
|
+
"$created" "$migrated" "$blocked" "$override_active" \
|
|
3369
3568
|
"$(json_escape "$template_arg")" "$(json_escape "${template_source:-}")"
|
|
3370
3569
|
}
|
|
3371
3570
|
|
|
@@ -3899,7 +4098,7 @@ cmd_slice_gate_stamp() {
|
|
|
3899
4098
|
--feature-dir) fd_arg="$2"; shift 2 ;;
|
|
3900
4099
|
# --flag=value: the findings are a JSON list, and any value may be empty.
|
|
3901
4100
|
--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 ;;
|
|
4101
|
+
--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
4102
|
*) echo "slice-gate-stamp: unknown arg '$1'" >&2; return 1 ;;
|
|
3904
4103
|
esac
|
|
3905
4104
|
done
|
|
@@ -4465,7 +4664,7 @@ cmd_cr_paths() {
|
|
|
4465
4664
|
case "$1" in
|
|
4466
4665
|
--cr-id) cr_id="$2"; shift 2 ;;
|
|
4467
4666
|
--feature-dir) feature_dir_arg="$2"; shift 2 ;;
|
|
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 ;;
|
|
4667
|
+
--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, delta_api_plan, wave, lanes[], lanes_csv, lanes_recorded, touches_backend, touches_frontend, predecessor_files[], predecessor_specs[], predecessor_docs[], sibling_files[], domain_model}"; return 0 ;;
|
|
4469
4668
|
*) echo "cr-paths: unknown arg '$1'" >&2; return 1 ;;
|
|
4470
4669
|
esac
|
|
4471
4670
|
done
|
|
@@ -4489,6 +4688,7 @@ dspec = os.path.join(cr_dir, "delta-spec.md")
|
|
|
4489
4688
|
dmodel = os.path.join(ctx, "domain-model.%s.delta.md" % cr)
|
|
4490
4689
|
be = os.path.join(cr_dir, "delta-backend-plan.md")
|
|
4491
4690
|
fe = os.path.join(cr_dir, "delta-frontend-plan.md")
|
|
4691
|
+
api = os.path.join(cr_dir, "delta-api-plan.md")
|
|
4492
4692
|
master = os.path.join(ctx, "domain-model.md")
|
|
4493
4693
|
wave = wave_of(dspec, wave_of(dmodel, 1))
|
|
4494
4694
|
# The lanes a change touches: read_change_lanes in python/aw-stack.py, the one reader of a
|
|
@@ -4531,6 +4731,8 @@ print(json.dumps({
|
|
|
4531
4731
|
"delta_model": rel(dmodel), "delta_model_exists": os.path.isfile(dmodel),
|
|
4532
4732
|
"delta_backend_plan": rel(be), "delta_backend_plan_exists": os.path.isfile(be),
|
|
4533
4733
|
"delta_frontend_plan": rel(fe), "delta_frontend_plan_exists": os.path.isfile(fe),
|
|
4734
|
+
# The change's API delta — merged into the feature's api-plan.md by finalize-delta.
|
|
4735
|
+
"delta_api_plan": rel(api), "delta_api_plan_exists": os.path.isfile(api),
|
|
4534
4736
|
"wave": wave,
|
|
4535
4737
|
# The lanes this change touches, from its delta-spec's **Lanes** line — every lane when
|
|
4536
4738
|
# it records none (lanes_recorded false).
|
|
@@ -4947,10 +5149,19 @@ snap = os.path.join(fd, "changes", cr, "pre-merge")
|
|
|
4947
5149
|
os.makedirs(snap, exist_ok=True)
|
|
4948
5150
|
spec = os.path.join(fd, "spec.md")
|
|
4949
5151
|
model = os.path.join(ctx, "domain-model.md")
|
|
5152
|
+
# Every document finalize-delta may rewrite: the two masters it merges into, the API plan it
|
|
5153
|
+
# merges the change's API into, the backend plan the first such merge moves the endpoints out
|
|
5154
|
+
# of, and the API draft it redraws.
|
|
5155
|
+
backend = next((os.path.join(fd, n) for n in ("backend-plan.md", "design.md")
|
|
5156
|
+
if os.path.isfile(os.path.join(fd, n))), os.path.join(fd, "backend-plan.md"))
|
|
5157
|
+
docs = ((spec, "spec.md"), (model, "domain-model.md"),
|
|
5158
|
+
(os.path.join(fd, "api-plan.md"), "api-plan.md"),
|
|
5159
|
+
(backend, os.path.basename(backend)),
|
|
5160
|
+
(os.path.join(fd, "api-draft.json"), "api-draft.json"))
|
|
4950
5161
|
copied = []
|
|
4951
|
-
# Flat names, not the source tree's shape:
|
|
4952
|
-
#
|
|
4953
|
-
for src, name in
|
|
5162
|
+
# Flat names, not the source tree's shape: the documents live at different levels, so a
|
|
5163
|
+
# nested copy would encode the layout of the day it was taken.
|
|
5164
|
+
for src, name in docs:
|
|
4954
5165
|
if os.path.isfile(src):
|
|
4955
5166
|
shutil.copy2(src, os.path.join(snap, name))
|
|
4956
5167
|
copied.append(name)
|
|
@@ -4967,7 +5178,7 @@ manifest = {
|
|
|
4967
5178
|
"version": version,
|
|
4968
5179
|
"context_version": header(model, "Context version"),
|
|
4969
5180
|
"files": copied,
|
|
4970
|
-
"sources": {
|
|
5181
|
+
"sources": {name: rel(src) for src, name in docs if name in copied},
|
|
4971
5182
|
# Stated in the file itself, because the restore is the reader's whole reason for
|
|
4972
5183
|
# opening it and the condition on it is not obvious from the content.
|
|
4973
5184
|
"note": ("Masters as they stood immediately before finalize-delta merged %s. "
|
|
@@ -5411,7 +5622,7 @@ cmd_gap_route() {
|
|
|
5411
5622
|
# (python/aw-plan-coverage.py), called identically by both shells.
|
|
5412
5623
|
cmd_plan_coverage() {
|
|
5413
5624
|
case "${1:-}" in
|
|
5414
|
-
--help|-h) echo "plan-coverage --plan <path> --kind backend|frontend [--mode bootstrap|delta] [--source-path <p>] [--test-path <p>] [--reported <json list>]... [--generated <json list>] [--list-only] -> {ok, outcome, total, names[], names_csv, elements[], gaps[], gap_names[], gap_count, gaps_text, message}"; return 0 ;;
|
|
5625
|
+
--help|-h) echo "plan-coverage --plan <path> --kind backend|frontend [--mode bootstrap|delta] [--api-plan <path>] [--source-path <p>] [--test-path <p>] [--reported <json list>]... [--generated <json list>] [--list-only] -> {ok, outcome, total, names[], names_csv, elements[], gaps[], gap_names[], gap_count, gaps_text, message}"; return 0 ;;
|
|
5415
5626
|
esac
|
|
5416
5627
|
local repo_root; repo_root=$(get_repo_root)
|
|
5417
5628
|
local core="$SCRIPT_DIR/../python/aw-plan-coverage.py"
|
|
@@ -5424,6 +5635,27 @@ cmd_plan_coverage() {
|
|
|
5424
5635
|
python3 "$core" --repo "$repo_root" "$@"
|
|
5425
5636
|
}
|
|
5426
5637
|
|
|
5638
|
+
# ──────────────────────────────── test-citations ────────────────────────────
|
|
5639
|
+
# Which of a lane plan's requirement ids no test of the slice names. A requirement whose
|
|
5640
|
+
# test was never written leaves the suite green, so analyze-slice and analyze-slice-delta
|
|
5641
|
+
# read the plan's FR- and BR- ids and look for each one in the slice's test files. Same
|
|
5642
|
+
# core as plan-coverage (python/aw-plan-coverage.py --citations), called identically by
|
|
5643
|
+
# both shells.
|
|
5644
|
+
cmd_test_citations() {
|
|
5645
|
+
case "${1:-}" in
|
|
5646
|
+
--help|-h) echo "test-citations --plan <path> --kind backend|frontend [--mode bootstrap|delta] [--paths <json list>]... [--source-path <p>] [--test-path <p>] [--spec <spec.md>] [--model <domain-model.md>] [--known-gaps <json list>]... -> {ok, all_cited, clean, outcome, checked, readable, advisory, required[], uncited[], uncited_csv, uncited_text, other_uncited[], stale[], findings[], tests_scanned, scoped, warning, message}"; return 0 ;;
|
|
5647
|
+
esac
|
|
5648
|
+
local repo_root; repo_root=$(get_repo_root)
|
|
5649
|
+
local core="$SCRIPT_DIR/../python/aw-plan-coverage.py"
|
|
5650
|
+
if [ ! -f "$core" ]; then
|
|
5651
|
+
echo "test-citations: core not found at $core" >&2; return 1
|
|
5652
|
+
fi
|
|
5653
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
5654
|
+
echo "test-citations: python3 is required" >&2; return 1
|
|
5655
|
+
fi
|
|
5656
|
+
python3 "$core" --repo "$repo_root" --citations "$@"
|
|
5657
|
+
}
|
|
5658
|
+
|
|
5427
5659
|
# Stamp a version into the MASTER documents' `**Version**:` headers — the
|
|
5428
5660
|
# deterministic twin of cr-archive's active_version.json bump. close-delta calls
|
|
5429
5661
|
# this with the version cr-archive just computed, so all THREE carriers of the
|
|
@@ -5476,8 +5708,9 @@ pat = re.compile(r'^(\*\*Version\*\*:[ \t]*)(\S+)(.*)$', re.M)
|
|
|
5476
5708
|
# spine cannot tolerate. A lane the feature does not have reports `missing` and nothing
|
|
5477
5709
|
# fails, which is also how the legacy names are carried — a feature not yet migrated to
|
|
5478
5710
|
# the plan names still gets stamped, and one that has been migrated reports the legacy
|
|
5479
|
-
# pair missing.
|
|
5480
|
-
for name in ("spec.md", "backend-plan.md", "frontend-plan.md", "design.md", "design.ui.md"
|
|
5711
|
+
# pair missing. The API plan is a master too, and moves with them.
|
|
5712
|
+
for name in ("spec.md", "backend-plan.md", "frontend-plan.md", "design.md", "design.ui.md",
|
|
5713
|
+
"api-plan.md"):
|
|
5481
5714
|
p = os.path.join(fd, name)
|
|
5482
5715
|
if not os.path.isfile(p):
|
|
5483
5716
|
missing.append(name); continue
|
|
@@ -5495,12 +5728,16 @@ for name in ("spec.md", "backend-plan.md", "frontend-plan.md", "design.md", "des
|
|
|
5495
5728
|
# found by its CR cell, not by position: rows are appended newest-last and a feature may
|
|
5496
5729
|
# carry many, so "the last row" would be right only until two changes were open at once.
|
|
5497
5730
|
# Only a cell still reading `(pending)` is written — a row already carrying a version
|
|
5498
|
-
# belongs to a change that closed, and re-stamping it would rewrite history.
|
|
5731
|
+
# belongs to a change that closed, and re-stamping it would rewrite history. The API plan's
|
|
5732
|
+
# row, which finalize-delta's API merge wrote, is completed the same way.
|
|
5499
5733
|
row_filled = False
|
|
5500
5734
|
row_version = ""
|
|
5735
|
+
rows_filled = []
|
|
5501
5736
|
if cr:
|
|
5502
|
-
|
|
5503
|
-
|
|
5737
|
+
for name in ("spec.md", "api-plan.md"):
|
|
5738
|
+
p = os.path.join(fd, name)
|
|
5739
|
+
if not os.path.isfile(p):
|
|
5740
|
+
continue
|
|
5504
5741
|
src = open(p, encoding="utf-8").read()
|
|
5505
5742
|
out_lines, changed = [], False
|
|
5506
5743
|
for line in src.splitlines(True):
|
|
@@ -5508,14 +5745,17 @@ if cr:
|
|
|
5508
5745
|
if (not changed) and cr in line and line.lstrip().startswith("|"):
|
|
5509
5746
|
cells = line.split("|")
|
|
5510
5747
|
if len(cells) > 2 and cells[1].strip().lower() in ("(pending)", "pending", "—", "-", ""):
|
|
5511
|
-
|
|
5748
|
+
if name == "spec.md":
|
|
5749
|
+
row_version = cells[1].strip()
|
|
5512
5750
|
cells[1] = " %s " % ver
|
|
5513
5751
|
line = "|".join(cells)
|
|
5514
5752
|
changed = True
|
|
5515
5753
|
out_lines.append(line)
|
|
5516
5754
|
if changed:
|
|
5517
5755
|
open(p, "w", encoding="utf-8").write("".join(out_lines))
|
|
5518
|
-
|
|
5756
|
+
rows_filled.append(name)
|
|
5757
|
+
if name == "spec.md":
|
|
5758
|
+
row_filled = True
|
|
5519
5759
|
|
|
5520
5760
|
print(json.dumps({"ok": True, "version": ver, "stamped": stamped,
|
|
5521
5761
|
"unchanged": unchanged, "missing": missing,
|
|
@@ -5524,7 +5764,10 @@ print(json.dumps({"ok": True, "version": ver, "stamped": stamped,
|
|
|
5524
5764
|
# the ordinary outcome for a re-run (already filled) and for a change
|
|
5525
5765
|
# whose merge predates the pending convention.
|
|
5526
5766
|
"cr_id": cr, "history_row_filled": row_filled,
|
|
5527
|
-
"history_row_was": row_version
|
|
5767
|
+
"history_row_was": row_version,
|
|
5768
|
+
# Every document whose pending row was completed: spec.md, and the API
|
|
5769
|
+
# plan when the change merged an API delta into it.
|
|
5770
|
+
"history_rows_filled": rows_filled}))
|
|
5528
5771
|
PY
|
|
5529
5772
|
}
|
|
5530
5773
|
|
|
@@ -5538,6 +5781,7 @@ case "$sub" in
|
|
|
5538
5781
|
set-domain) cmd_set_domain "$@" ;;
|
|
5539
5782
|
stack-list) cmd_stack_list "$@" ;;
|
|
5540
5783
|
stack-detect) cmd_stack_detect "$@" ;;
|
|
5784
|
+
stack-rules) cmd_stack_rules "$@" ;;
|
|
5541
5785
|
lane-declare) cmd_lane_declare "$@" ;;
|
|
5542
5786
|
lane-resolve) cmd_lane_resolve "$@" ;;
|
|
5543
5787
|
lane-verify) cmd_lane_verify "$@" ;;
|
|
@@ -5551,6 +5795,9 @@ case "$sub" in
|
|
|
5551
5795
|
contract-client) cmd_contract_client "$@" ;;
|
|
5552
5796
|
contract-check) cmd_contract_check "$@" ;;
|
|
5553
5797
|
contract-impact) cmd_contract_impact "$@" ;;
|
|
5798
|
+
contract-verify) cmd_contract_verify "$@" ;;
|
|
5799
|
+
contract-merge) cmd_contract_merge "$@" ;;
|
|
5800
|
+
api-split) cmd_api_split "$@" ;;
|
|
5554
5801
|
list-contexts) cmd_list_contexts "$@" ;;
|
|
5555
5802
|
set-context) cmd_set_context "$@" ;;
|
|
5556
5803
|
list-features) cmd_list_features "$@" ;;
|
|
@@ -5588,11 +5835,14 @@ case "$sub" in
|
|
|
5588
5835
|
context-version-bump) cmd_context_version_bump "$@" ;;
|
|
5589
5836
|
context-paths) cmd_context_paths "$@" ;;
|
|
5590
5837
|
model-scaffold) cmd_model_scaffold "$@" ;;
|
|
5838
|
+
model-guard-snapshot) cmd_model_guard snapshot "$@" ;;
|
|
5839
|
+
model-guard-check) cmd_model_guard check "$@" ;;
|
|
5591
5840
|
version-stamp) cmd_version_stamp "$@" ;;
|
|
5592
5841
|
plan-gaps) cmd_plan_gaps "$@" ;;
|
|
5593
5842
|
plan-gate-stamp) cmd_plan_gate_stamp "$@" ;;
|
|
5594
5843
|
plan-gate-check) cmd_plan_gate_check "$@" ;;
|
|
5595
5844
|
plan-coverage) cmd_plan_coverage "$@" ;;
|
|
5845
|
+
test-citations) cmd_test_citations "$@" ;;
|
|
5596
5846
|
gap-route) cmd_gap_route "$@" ;;
|
|
5597
5847
|
checklist-read) cmd_checklist_read "$@" ;;
|
|
5598
5848
|
checklist-apply) cmd_checklist_apply "$@" ;;
|