aw-spec-cli 0.1.26 → 0.1.28
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/.agents/skills/appweaver/SKILL.md +7 -47
- package/assets/.appweaver/ORCH-DIALECT.md +13 -9
- package/assets/.appweaver/RULE-PARTITION.md +11 -12
- package/assets/.appweaver/STACK-PROFILES.md +260 -108
- package/assets/.appweaver/domain.json +1 -1
- package/assets/.appweaver/interpreter/run-skill.py +28 -7
- package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +38 -10
- package/assets/.appweaver/orchestrator/LAUNCHER.md +88 -0
- package/assets/.appweaver/orchestrator/README.md +120 -53
- package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +1 -1
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +17 -7
- package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +6 -4
- package/assets/.appweaver/orchestrator/modules/foundation-lane.orch.yaml +237 -0
- package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +392 -0
- package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +548 -0
- package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/orchestrator.py +162 -63
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +5 -4
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +3 -1
- package/assets/.appweaver/orchestrator/rev-templates/domain-constitution.skeleton.md +2 -2
- package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +2 -2
- package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +21 -13
- package/assets/.appweaver/orchestrator/rev-templates/frontend-rule-file.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +28 -31
- package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +2 -2
- package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-constitution.skeleton.md +3 -3
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +4 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
- package/assets/.appweaver/orchestrator/workflows/_smoke.orch.yaml +41 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-add-component.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +17 -16
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +7 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +7 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +2 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +61 -12
- package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +172 -293
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +253 -44
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +72 -71
- package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +38 -14
- package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +26 -2
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +21 -15
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +14 -14
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +6 -6
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +371 -131
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +293 -76
- package/assets/.appweaver/scripts/python/aw-foundation.py +885 -20
- package/assets/.appweaver/scripts/python/aw-inventory.py +28 -21
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +2 -2
- package/assets/.appweaver/scripts/python/aw-rev-constitution.py +6 -6
- package/assets/.appweaver/scripts/python/aw-stack.py +676 -99
- package/assets/.appweaver/stack-profiles/angular.json +2 -1
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +2 -1
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +18 -3
- package/assets/.appweaver/stack-profiles/go-chi.json +6 -1
- package/assets/.appweaver/stack-profiles/java-spring.json +14 -5
- package/assets/.appweaver/stack-profiles/nextjs.json +2 -1
- package/assets/.appweaver/stack-profiles/node-express.json +8 -2
- package/assets/.appweaver/stack-profiles/python-fastapi.json +9 -2
- package/assets/.appweaver/stack-profiles/react-vite.json +2 -1
- package/assets/.appweaver/stack-profiles/sveltekit.json +2 -1
- package/assets/.appweaver/stack-profiles/vue-vite.json +2 -1
- package/assets/.appweaver/stacks.json +40 -1
- package/assets/.appweaver/templates/constitution-ui-template.md +2 -2
- package/assets/.appweaver/templates/delta-design-template.md +7 -5
- package/assets/.appweaver/templates/design-template.md +3 -0
- package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
- package/assets/.appweaver/templates/ui-design-template.md +6 -4
- package/assets/.claude/skills/appweaver/SKILL.md +7 -46
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.Reference.rules.json +31 -0
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.lane.json +6 -0
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.Reference.rules.json +16 -0
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.lane.json +6 -0
- package/assets/foundations/foundation.spec.template.txt +98 -0
- package/assets/foundations/java/Hexagonal/Hexagonal.lane.json +6 -0
- package/assets/foundations/java/Hexagonal/Hexagonal.pack.txt +8 -1
- package/assets/foundations/java/Hexagonal/unpack-foundation.sh +4 -5
- package/assets/foundations/node/NextAppRouter/NextAppRouter.spec.txt +68 -0
- package/assets/guardrails/README.md +31 -17
- package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain-rule-bindings.md +25 -13
- package/assets/guardrails/project-specific/tech-rule-bindings.md +296 -0
- package/assets/guardrails/technology/dotnet-webapi/PACKAGING.md +58 -4
- package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/dependency-policy.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
- package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
- package/assets/guardrails/technology/nextjs/api-client.md +1 -1
- package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
- package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
- package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
- package/assets/guardrails/technology/nextjs/client-security.md +1 -1
- package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
- package/assets/guardrails/technology/nextjs/component-conventions.md +2 -2
- package/assets/guardrails/technology/nextjs/design-system.md +1 -1
- package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
- package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
- package/assets/guardrails/technology/nextjs/frontend-testing.md +1 -1
- package/assets/guardrails/technology/nextjs/i18n.md +1 -1
- package/assets/guardrails/technology/nextjs/observability.md +1 -1
- package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
- package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
- package/assets/guardrails/technology/nextjs/state-management.md +1 -1
- package/assets/guardrails/technology/nextjs/ui-constitution.md +3 -3
- package/package.json +1 -1
- package/assets/.agents/skills/appweaver-demo/SKILL.md +0 -59
- package/assets/.appweaver/orchestrator/.smoke-out/smoke.md +0 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-demo.orch.yaml +0 -100
- package/assets/.claude/skills/appweaver-demo/SKILL.md +0 -59
- package/assets/.kilocode/agent.json +0 -28
- package/assets/.kilocode/workflows/appweaver-demo.md +0 -59
- package/assets/.kilocode/workflows/appweaver.md +0 -55
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +0 -271
- /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/domain/domain-requirements.md +0 -0
- /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/frontend/ui-requirements.md +0 -0
- /package/assets/guardrails/{projects/dotnet-default-project → project-specific}/service/technical-requirements.md +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# aw-spec-actions.sh — common action dispatcher for the aw-spec orchestrator.
|
|
4
4
|
#
|
|
5
|
-
# ONE shared script for every integration (.claude / .agents / .
|
|
5
|
+
# ONE shared script for every integration (.claude / .agents / .kilo). The
|
|
6
6
|
# orchestrator workflows reach these subcommands through `execute_script` steps.
|
|
7
7
|
# Each subcommand performs deterministic, governance-safe work and prints a single
|
|
8
8
|
# JSON object on stdout. NONE of these actions author spec/code content — content
|
|
@@ -47,8 +47,16 @@
|
|
|
47
47
|
# Always exits 0 — the workflow's verify_context owns the stop)
|
|
48
48
|
# lane-probe [--kind <k>] [--hint <text>] -> {declared, declared_profile, hint_profile, hint_terms[], signals[], any, evidence}
|
|
49
49
|
# (the FILE signals for "does this lane exist"; resolve-tech carries the rule signal)
|
|
50
|
+
# lane-set (--id <lane>|--kind <k>) [--set KEY=VALUE]… [--unset KEY]… -> {ok, lane_id, set, removed, written}
|
|
51
|
+
# (KEY: contract_run, contract, client; an empty VALUE changes nothing)
|
|
52
|
+
# contract-export [--timeout <s>] -> {ok, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}
|
|
53
|
+
# (start the service lane with contract_run, save the document at contract, stop it)
|
|
54
|
+
# contract-client -> run-phase result for the frontend lane's `client` command
|
|
55
|
+
# contract-check --plan <p> [--doc <d>] -> {ok, cited, ids[], missing[], uncited, doc_exists, reason}
|
|
50
56
|
# list-features [--preselect <name>] [--bundle <dir>] -> {features[], names[], count, select_options[], sentinel} (specs/ ∪ features the OKF concepts serve)
|
|
51
57
|
# set-feature --feature <name> -> {feature, feature_dir, spec_file, existed, created, spec_exists}
|
|
58
|
+
# default-scope --context <name> --feature <name> -> {created, skipped_reason, context_dir, feature_dir}
|
|
59
|
+
# (creates and activates both only on a project with no specs yet)
|
|
52
60
|
# version-read [--feature-dir <path>] -> {feature, version, active_change, exists, bootstrap_pending}
|
|
53
61
|
# version-init [--feature-dir <path>] [--bootstrap] -> {…, created, bootstrap_pending} (create active_version.json v1.0; --bootstrap marks a FIRST draft in progress)
|
|
54
62
|
# version-bootstrap-done [--feature-dir <path>] -> {…, cleared} (the ONLY clearer of the --bootstrap marker; call it when the first draft is finished)
|
|
@@ -63,11 +71,28 @@
|
|
|
63
71
|
# -> {ok, total, names[], names_csv, elements[], gaps[], gap_count, gaps_text}
|
|
64
72
|
# (which plan elements the lane's code names and the model reported done)
|
|
65
73
|
# detect-project -> {scaffolded, markers[]}
|
|
66
|
-
# foundation-list [--
|
|
74
|
+
# foundation-list [--kind <k>] [--profile <id>] [--ecosystem <e>] [--lane-id <id>] [--args '<F> <A>']
|
|
75
|
+
# -> {options[], entries[], count, foundations[], specs[], preselect_option, preselect_app, rejected_out}
|
|
76
|
+
# (one lane's pick list: its packs + specs, then [manual spec] and [skip])
|
|
77
|
+
# foundation-choice --selection <option> [same filters as foundation-list]
|
|
78
|
+
# -> {ok, route (pack|spec|manual|skip), name, file, reason}
|
|
79
|
+
# foundation-spec-capture --lane <id> [--kind <k>] (--file <path> | --input <path|text>) [--app <A>]
|
|
80
|
+
# -> {ok, spec_path, source, chars, title, reason, contract_run, contract, client, settings_text}
|
|
81
|
+
# foundation-lane-files --source <p> [--test <p>] [--workdir <p>] [--profile <id>]
|
|
82
|
+
# -> {has_files, count, sample[], generated[], root_files[], folders[], workdir, summary}
|
|
83
|
+
# foundation-snapshot --out <repo-relative file> -> {ok, snapshot, entries}
|
|
84
|
+
# foundation-outside --snapshot <file> --lane <lane root folder> [--undo]
|
|
85
|
+
# -> {clean, created[], changed[], deleted[], removed[], summary}
|
|
67
86
|
# foundation-locate --name <F> --app <A> [--out <d>]
|
|
68
|
-
# -> {ok, reason, name, app, out_dir, pack_file, unpacker, runner, sentinel, file_count, available[]}
|
|
69
|
-
# foundation-unpack --name <F> --app <A> --out <d> [--dry-run] [--force <yes|no>] [--reference]
|
|
70
|
-
# -> {ok, mode, written, has_collisions, stdout}
|
|
87
|
+
# -> {ok, reason, name, app, out_dir, pack_file, unpacker, runner, sentinel, file_count, has_reference, available[]}
|
|
88
|
+
# foundation-unpack --name <F> --app <A> --out <d> [--test-path <d>] [--dry-run] [--force <yes|no>] [--reference]
|
|
89
|
+
# -> {ok, mode, written, has_collisions, tests_moved, tests_message, stdout}
|
|
90
|
+
# foundation-rules --name <F> --app <A> --out <lane root folder> [--reference] [--state <folder>]
|
|
91
|
+
# -> {ok, reason, rules_file, rules, merges[], merge_count, stale[], note}
|
|
92
|
+
# (what the pack's <pack name>.rules.json asks of this unpack; puts the files to merge aside)
|
|
93
|
+
# foundation-rules-done [--state <folder>] -> {ok, removed}
|
|
94
|
+
# foundation-pack-lane --name <F> --app <A> -> {found, file, contract_run, contract, client, reason}
|
|
95
|
+
# (the lane settings in the pack's <pack name>.lane.json, sentinel renamed)
|
|
71
96
|
# foundation-verify --out <d> --sentinel <s> -> {clean, residue[]}
|
|
72
97
|
# list-components -> {components[]} (foundations/dotnet/components/* with integration.md)
|
|
73
98
|
# find-host-project -> {found, program_cs, project_dir, csproj}
|
|
@@ -86,7 +111,9 @@
|
|
|
86
111
|
# (design-extract / design-reconcile / design-paths are RETIRED — the path-tagged
|
|
87
112
|
# four-document model is gone. "Which files is this slice made of" is now an OKF
|
|
88
113
|
# query: aw-xref concept-paths / concept-upsert --merge.)
|
|
89
|
-
# build-compile [--feature-dir <path>]
|
|
114
|
+
# build-compile [--feature-dir <path>] [--kind backend|frontend]
|
|
115
|
+
# -> {ok, tool, ran, cmd, log_tail}
|
|
116
|
+
# (--kind builds that one declared lane; ignored when no lane is declared)
|
|
90
117
|
# test-run [--feature-dir <path>] -> {ok, tool, ran, cmd, log_tail,
|
|
91
118
|
# failing_excerpt, failing_lanes[],
|
|
92
119
|
# no_setup, setup_reason, setup_missing[]}
|
|
@@ -163,7 +190,7 @@ cmd_resolve_domain() {
|
|
|
163
190
|
fi
|
|
164
191
|
fi
|
|
165
192
|
|
|
166
|
-
local rulemap="$repo_root/guardrails
|
|
193
|
+
local rulemap="$repo_root/guardrails/$domain/domain-rule-bindings.md"
|
|
167
194
|
local rulemap_exists=false
|
|
168
195
|
[ -n "$domain" ] && [ -f "$rulemap" ] && rulemap_exists=true
|
|
169
196
|
|
|
@@ -188,7 +215,7 @@ cmd_resolve_domain() {
|
|
|
188
215
|
|
|
189
216
|
# ──────────────────────────────── resolve-tech ─────────────────────────────
|
|
190
217
|
# Technical rule-file mapping — parallel to resolve-domain, but reads
|
|
191
|
-
# guardrails
|
|
218
|
+
# guardrails/<project>/tech-rule-bindings.md. The TECHNICAL rule files live in the
|
|
192
219
|
# shared per-stack corpora at guardrails/technology/<stack>/ (rules) and
|
|
193
220
|
# guardrails/technology/<stack>/examples/ (the worked sample for a rule file), and are
|
|
194
221
|
# configured per-command in this map, so they can be tuned per project WITHOUT editing the
|
|
@@ -239,7 +266,7 @@ repo = os.environ["AW_REPO"]
|
|
|
239
266
|
domain = os.environ.get("AW_DOMAIN", "")
|
|
240
267
|
command = os.environ.get("AW_COMMAND", "")
|
|
241
268
|
|
|
242
|
-
techmap = os.path.join(repo, "guardrails",
|
|
269
|
+
techmap = os.path.join(repo, "guardrails", domain, "tech-rule-bindings.md")
|
|
243
270
|
exists = bool(domain) and os.path.isfile(techmap)
|
|
244
271
|
|
|
245
272
|
# WHAT A PLACEHOLDER IS FOR. A technical rule corpus is stack-partitioned —
|
|
@@ -638,11 +665,12 @@ EOF
|
|
|
638
665
|
}
|
|
639
666
|
|
|
640
667
|
# ─────────────────────────────── list-domains ──────────────────────────────
|
|
641
|
-
# The
|
|
642
|
-
#
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
# map several steps before the technical one and a half-built
|
|
668
|
+
# The projects: immediate subdirs of guardrails/ that carry a rule map. The map is what
|
|
669
|
+
# makes a directory a project, exactly as a *.pack.txt is what makes a foundations/
|
|
670
|
+
# directory a foundation — the shared corpora technology/ and domain/ sit beside the
|
|
671
|
+
# projects and carry no map, so they are never listed. EITHER map qualifies, because
|
|
672
|
+
# onboard writes the domain map several steps before the technical one and a half-built
|
|
673
|
+
# project must stay visible.
|
|
646
674
|
cmd_list_domains() {
|
|
647
675
|
while [ $# -gt 0 ]; do
|
|
648
676
|
case "$1" in
|
|
@@ -651,7 +679,7 @@ cmd_list_domains() {
|
|
|
651
679
|
esac
|
|
652
680
|
done
|
|
653
681
|
local repo_root; repo_root=$(get_repo_root)
|
|
654
|
-
local base="$repo_root/guardrails
|
|
682
|
+
local base="$repo_root/guardrails"
|
|
655
683
|
local -a domains=()
|
|
656
684
|
if [ -d "$base" ]; then
|
|
657
685
|
local d
|
|
@@ -677,7 +705,7 @@ cmd_set_domain() {
|
|
|
677
705
|
[ -n "$domain" ] || { echo "set-domain: --domain is required" >&2; return 1; }
|
|
678
706
|
|
|
679
707
|
local repo_root; repo_root=$(get_repo_root)
|
|
680
|
-
local map_rel="guardrails
|
|
708
|
+
local map_rel="guardrails/$domain/domain-rule-bindings.md"
|
|
681
709
|
local map_exists=false
|
|
682
710
|
[ -f "$repo_root/$map_rel" ] && map_exists=true
|
|
683
711
|
|
|
@@ -697,9 +725,10 @@ cmd_set_domain() {
|
|
|
697
725
|
# The lane model. All four delegate to the shared python core so the bash and
|
|
698
726
|
# powershell twins cannot drift: the JSON contract has one implementation.
|
|
699
727
|
#
|
|
700
|
-
# lane-declare
|
|
701
|
-
#
|
|
702
|
-
#
|
|
728
|
+
# lane-declare writes WHOLE lanes into .appweaver/stacks.json and has exactly one caller
|
|
729
|
+
# (aw-spec-select-stack); lane-set changes only the API-contract keys on a lane already
|
|
730
|
+
# there (foundation and implement call it). lane-resolve can never write — which is why
|
|
731
|
+
# these are separate subcommands rather than one with a mode flag.
|
|
703
732
|
_aw_stack_py() {
|
|
704
733
|
local repo_root; repo_root=$(get_repo_root)
|
|
705
734
|
local core="$SCRIPT_DIR/../python/aw-stack.py"
|
|
@@ -728,7 +757,7 @@ except Exception:
|
|
|
728
757
|
' 2>/dev/null || echo ""
|
|
729
758
|
}
|
|
730
759
|
|
|
731
|
-
# The declared
|
|
760
|
+
# The declared LANE ROOT FOLDER of a lane kind — the field asked for most often.
|
|
732
761
|
_aw_lane_root() { _aw_lane_field "$1" root; }
|
|
733
762
|
|
|
734
763
|
# Every ecosystem the declared lanes belong to, one per line, deduped and empty when
|
|
@@ -813,12 +842,66 @@ cmd_lane_resolve() {
|
|
|
813
842
|
_aw_stack_py resolve "${fwd[@]}"
|
|
814
843
|
}
|
|
815
844
|
|
|
845
|
+
# Change the API-contract keys (contract_run, contract, client) on a lane already
|
|
846
|
+
# declared, leaving the rest of it alone. `--set KEY=` with an empty value changes
|
|
847
|
+
# nothing; `--unset KEY` removes one. Callers: foundation (a pack's lane settings) and
|
|
848
|
+
# implement (the client command a developer supplies).
|
|
849
|
+
cmd_lane_set() {
|
|
850
|
+
local -a fwd=()
|
|
851
|
+
while [ $# -gt 0 ]; do
|
|
852
|
+
case "$1" in
|
|
853
|
+
--id|--kind|--set|--unset) fwd+=("$1" "$2"); shift 2 ;;
|
|
854
|
+
--help|-h) echo "lane-set (--id <lane>|--kind backend|frontend) [--set KEY=VALUE]… [--unset KEY]… (KEY: contract_run, contract, client)"; return 0 ;;
|
|
855
|
+
*) echo "lane-set: unknown arg '$1'" >&2; return 1 ;;
|
|
856
|
+
esac
|
|
857
|
+
done
|
|
858
|
+
_aw_stack_py set "${fwd[@]}"
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
# ─────────────── contract-export / contract-client / contract-check ───────────────
|
|
862
|
+
# The API contract between the lanes: export the service lane's API document from the
|
|
863
|
+
# running service, generate the frontend lane's client from it, and check the operation
|
|
864
|
+
# ids a frontend plan cites against it. All three delegate to aw-stack.py.
|
|
865
|
+
cmd_contract_export() {
|
|
866
|
+
local -a fwd=()
|
|
867
|
+
while [ $# -gt 0 ]; do
|
|
868
|
+
case "$1" in
|
|
869
|
+
--timeout) fwd+=("$1" "$2"); shift 2 ;;
|
|
870
|
+
--help|-h) echo "contract-export [--timeout <seconds>] -> {ok, ran, cmd, url, doc, doc_exists, operations, unnamed, already_running, reason, log_tail}"; return 0 ;;
|
|
871
|
+
*) echo "contract-export: unknown arg '$1'" >&2; return 1 ;;
|
|
872
|
+
esac
|
|
873
|
+
done
|
|
874
|
+
_aw_stack_py contract-export "${fwd[@]}"
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
cmd_contract_client() {
|
|
878
|
+
while [ $# -gt 0 ]; do
|
|
879
|
+
case "$1" in
|
|
880
|
+
--help|-h) echo "contract-client -> run-phase --phase client for the frontend lane"; return 0 ;;
|
|
881
|
+
*) echo "contract-client: unknown arg '$1'" >&2; return 1 ;;
|
|
882
|
+
esac
|
|
883
|
+
done
|
|
884
|
+
_aw_stack_py run-phase --phase client --kind frontend
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
cmd_contract_check() {
|
|
888
|
+
local -a fwd=()
|
|
889
|
+
while [ $# -gt 0 ]; do
|
|
890
|
+
case "$1" in
|
|
891
|
+
--plan|--doc) fwd+=("$1" "$2"); shift 2 ;;
|
|
892
|
+
--help|-h) echo "contract-check --plan <frontend plan> [--doc <api document>] -> {ok, cited, ids, missing, uncited, doc_exists, reason}"; return 0 ;;
|
|
893
|
+
*) echo "contract-check: unknown arg '$1'" >&2; return 1 ;;
|
|
894
|
+
esac
|
|
895
|
+
done
|
|
896
|
+
_aw_stack_py contract-check "${fwd[@]}"
|
|
897
|
+
}
|
|
898
|
+
|
|
816
899
|
# The gate: is the declaration USABLE for the phase about to run. --require names a
|
|
817
900
|
# lane kind the phase cannot run without and is repeatable; every DECLARED lane is
|
|
818
|
-
# checked for a sound
|
|
901
|
+
# checked for a sound lane root folder whether or not it was required, because a stack
|
|
819
902
|
# named against a directory that is not there is a mistake in every phase.
|
|
820
|
-
# --allow-missing-root is the greenfield state, where select-stack declares a
|
|
821
|
-
#
|
|
903
|
+
# --allow-missing-root is the greenfield state, where select-stack declares a lane root
|
|
904
|
+
# folder before implement creates it.
|
|
822
905
|
cmd_lane_verify() {
|
|
823
906
|
local -a fwd=()
|
|
824
907
|
while [ $# -gt 0 ]; do
|
|
@@ -949,6 +1032,13 @@ PY
|
|
|
949
1032
|
# file; a new one is created. The context directory is created but domain-model.md is
|
|
950
1033
|
# NOT — that document is authored by specify, which is the only writer of the model,
|
|
951
1034
|
# and an empty placeholder would read as "the model exists" to every later gate.
|
|
1035
|
+
#
|
|
1036
|
+
# The active feature belongs to the context it was created in. When the selected
|
|
1037
|
+
# context does not contain it, .appweaver/feature.json is cleared, so the next
|
|
1038
|
+
# feature-scoped command stops with "No active feature" instead of writing into the
|
|
1039
|
+
# context the developer just left (and adopt with no argument does not recreate that
|
|
1040
|
+
# feature's name in the new context). A flat specs/<Feature> is outside every context
|
|
1041
|
+
# and is cleared the same way.
|
|
952
1042
|
cmd_set_context() {
|
|
953
1043
|
local raw=""
|
|
954
1044
|
while [ $# -gt 0 ]; do
|
|
@@ -991,11 +1081,20 @@ cmd_set_context() {
|
|
|
991
1081
|
"$(json_escape "$context")" "$(json_escape "$context_dir_rel")" > "$cj"
|
|
992
1082
|
fi
|
|
993
1083
|
|
|
1084
|
+
local active_fd; active_fd=$(read_feature_json_feature_directory "$repo_root")
|
|
1085
|
+
active_fd="${active_fd#"$repo_root/"}"; active_fd="${active_fd#./}"; active_fd="${active_fd%/}"
|
|
1086
|
+
local feature_cleared=false cleared_feature=""
|
|
1087
|
+
if [ -n "$active_fd" ] && [[ "$active_fd" != "$context_dir_rel/"* ]]; then
|
|
1088
|
+
_persist_feature_json "$repo_root" ""
|
|
1089
|
+
feature_cleared=true; cleared_feature="$active_fd"
|
|
1090
|
+
fi
|
|
1091
|
+
|
|
994
1092
|
local created=true; [ "$existed" = true ] && created=false
|
|
995
1093
|
local dm_exists=false; [ -f "$context_dir/domain-model.md" ] && dm_exists=true
|
|
996
|
-
printf '{"context":"%s","context_dir":"%s","domain_model":"%s","existed":%s,"created":%s,"domain_model_exists":%s,"written":true}\n' \
|
|
1094
|
+
printf '{"context":"%s","context_dir":"%s","domain_model":"%s","existed":%s,"created":%s,"domain_model_exists":%s,"feature_cleared":%s,"cleared_feature":"%s","written":true}\n' \
|
|
997
1095
|
"$(json_escape "$context")" "$(json_escape "$context_dir_rel")" \
|
|
998
|
-
"$(json_escape "$context_dir_rel/domain-model.md")" "$existed" "$created" "$dm_exists"
|
|
1096
|
+
"$(json_escape "$context_dir_rel/domain-model.md")" "$existed" "$created" "$dm_exists" \
|
|
1097
|
+
"$feature_cleared" "$(json_escape "$cleared_feature")"
|
|
999
1098
|
}
|
|
1000
1099
|
|
|
1001
1100
|
# ══════════════════════ domain model — context-level helpers ════════════════
|
|
@@ -1460,6 +1559,47 @@ PY
|
|
|
1460
1559
|
"$(json_escape "$context")"
|
|
1461
1560
|
}
|
|
1462
1561
|
|
|
1562
|
+
# ─────────────────────────────── default-scope ─────────────────────────────
|
|
1563
|
+
# The bounded context and feature a new project starts in, so a simple project goes
|
|
1564
|
+
# from /appweaver constitution straight to specify. Acts only on a project with no specs
|
|
1565
|
+
# yet: no directory under specs/ (no context, no feature) and no active feature. Every
|
|
1566
|
+
# other state is reported as skipped and left untouched, so a re-run of constitution
|
|
1567
|
+
# never moves the active feature and an onboarded brownfield tree is never added to.
|
|
1568
|
+
# Creation goes through set-context and set-feature, so the folders and both pointer
|
|
1569
|
+
# files are written exactly as select-bounded-context and select-feature write them.
|
|
1570
|
+
# context_dir / feature_dir report the active pair after the call, created or not.
|
|
1571
|
+
cmd_default_scope() {
|
|
1572
|
+
local ctx="" feat=""
|
|
1573
|
+
while [ $# -gt 0 ]; do
|
|
1574
|
+
case "$1" in
|
|
1575
|
+
--context) ctx="$2"; shift 2 ;;
|
|
1576
|
+
--feature) feat="$2"; shift 2 ;;
|
|
1577
|
+
--help|-h) echo "default-scope --context <name> --feature <name> (create and activate both on a project with no specs yet)"; return 0 ;;
|
|
1578
|
+
*) echo "default-scope: unknown arg '$1'" >&2; return 1 ;;
|
|
1579
|
+
esac
|
|
1580
|
+
done
|
|
1581
|
+
{ [ -n "$ctx" ] && [ -n "$feat" ]; } || { echo "default-scope: --context and --feature are required" >&2; return 1; }
|
|
1582
|
+
|
|
1583
|
+
local repo_root; repo_root=$(get_repo_root)
|
|
1584
|
+
local reason=""
|
|
1585
|
+
if [ -d "$repo_root/specs" ] && [ -n "$(find "$repo_root/specs" -mindepth 1 -maxdepth 1 -type d -print -quit 2>/dev/null)" ]; then
|
|
1586
|
+
reason="specs/ already holds a bounded context or a feature"
|
|
1587
|
+
elif [ -n "$(read_feature_json_feature_directory "$repo_root")" ]; then
|
|
1588
|
+
reason="a feature is already active in .appweaver/feature.json"
|
|
1589
|
+
fi
|
|
1590
|
+
|
|
1591
|
+
local created=false
|
|
1592
|
+
if [ -z "$reason" ]; then
|
|
1593
|
+
cmd_set_context --context "$ctx" >/dev/null || return 1
|
|
1594
|
+
cmd_set_feature --feature "$feat" >/dev/null || return 1
|
|
1595
|
+
created=true
|
|
1596
|
+
fi
|
|
1597
|
+
printf '{"created":%s,"skipped_reason":"%s","context_dir":"%s","feature_dir":"%s"}\n' \
|
|
1598
|
+
"$created" "$(json_escape "$reason")" \
|
|
1599
|
+
"$(json_escape "$(read_context_json_context_directory "$repo_root")")" \
|
|
1600
|
+
"$(json_escape "$(read_feature_json_feature_directory "$repo_root")")"
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1463
1603
|
# ═══════════════════════════ active_version.json ════════════════════════════
|
|
1464
1604
|
# PER-FEATURE state at <feature_dir>/active_version.json:
|
|
1465
1605
|
# {feature, version, active_change, updated}
|
|
@@ -1938,7 +2078,7 @@ cmd_detect_project() {
|
|
|
1938
2078
|
local repo_root; repo_root=$(get_repo_root)
|
|
1939
2079
|
local -a markers=()
|
|
1940
2080
|
local scope="repo" start="$repo_root" rel
|
|
1941
|
-
# A lane
|
|
2081
|
+
# A lane root folder arrives as the value declare stored, so it may carry a
|
|
1942
2082
|
# trailing slash; an option label ("path — why") never reaches a script argument.
|
|
1943
2083
|
rel="${scope_root#./}"; rel="${rel%/}"
|
|
1944
2084
|
if [ -n "$rel" ] && [ -d "$repo_root/$rel" ]; then
|
|
@@ -1971,10 +2111,12 @@ cmd_detect_project() {
|
|
|
1971
2111
|
# they scaffold. Names are unique across stacks, so callers pass a bare <Name> and
|
|
1972
2112
|
# the lookup searches every stack.
|
|
1973
2113
|
|
|
1974
|
-
# Echo "<stack>/<Name>" for each foundation directory, one per line
|
|
1975
|
-
#
|
|
1976
|
-
#
|
|
1977
|
-
# foundations in
|
|
2114
|
+
# Echo "<stack>/<Name>" for each PACK foundation directory, one per line — the scan
|
|
2115
|
+
# foundation-locate and foundation-unpack resolve a pack name through. A stack also holds
|
|
2116
|
+
# siblings that are not foundations (components/), so carrying a *.pack.txt is what makes
|
|
2117
|
+
# a directory one here — without that test they surface as bundle-less foundations in
|
|
2118
|
+
# every lookup. A directory carrying only *.spec.txt is listed by foundation-list
|
|
2119
|
+
# (aw-foundation.py) and never unpacked, so it is deliberately absent from this scan.
|
|
1978
2120
|
_foundation_dirs() {
|
|
1979
2121
|
local repo_root="$1" s d
|
|
1980
2122
|
[ -d "$repo_root/foundations" ] || return 0
|
|
@@ -1997,98 +2139,147 @@ _foundation_stack() {
|
|
|
1997
2139
|
}
|
|
1998
2140
|
|
|
1999
2141
|
# ──────────────────────────── foundation-list ──────────────────────────────
|
|
2000
|
-
#
|
|
2001
|
-
# <Name>.pack.txt)
|
|
2002
|
-
#
|
|
2003
|
-
#
|
|
2004
|
-
#
|
|
2142
|
+
# What ONE lane can be scaffolded from, as a pick list — deterministic, no LLM. Every
|
|
2143
|
+
# pack (<Name>.pack.txt) and every spec (*.spec.txt) under foundations/<ecosystem>/<Name>/
|
|
2144
|
+
# that fits the lane, then "[manual spec]" and "[skip]". The scan, the header filters
|
|
2145
|
+
# (# kind / # profiles) and the option labels live in aw-foundation.py so the two shells
|
|
2146
|
+
# answer from one implementation.
|
|
2147
|
+
#
|
|
2148
|
+
# WHICH entries a lane may be offered. A pack is a whole compiling project in one
|
|
2149
|
+
# ecosystem's build system, and an ecosystem is shared by stacks that cannot use each
|
|
2150
|
+
# other's scaffold, so three filters apply: --ecosystem (the lane's), --kind (backend |
|
|
2151
|
+
# frontend; an entry with no `# kind:` is backend) and --profile (only when the entry
|
|
2152
|
+
# names profiles). Without --ecosystem the filter falls back to every declared lane's
|
|
2153
|
+
# ecosystem, and to nothing at all on a bare repo; an EMPTY --ecosystem filters to none.
|
|
2005
2154
|
#
|
|
2006
|
-
#
|
|
2007
|
-
#
|
|
2008
|
-
#
|
|
2009
|
-
#
|
|
2010
|
-
# a path argument that is quietly dropped is how someone believes they scaffolded a
|
|
2011
|
-
# sandbox and finds they overwrote their service.
|
|
2155
|
+
# --args "<Foundation> <App>" is the direct-argument fast path: preselect_option is the
|
|
2156
|
+
# option for that name when this lane offers it; preselect_app carries the 2nd token. A
|
|
2157
|
+
# THIRD token comes back as `rejected_out` — a scaffold lands in its lane root folder,
|
|
2158
|
+
# so there is no caller-chosen destination to accept, and the workflow refuses it by name.
|
|
2012
2159
|
cmd_foundation_list() {
|
|
2013
|
-
local
|
|
2160
|
+
local -a fwd=()
|
|
2161
|
+
local eco_given=false
|
|
2014
2162
|
while [ $# -gt 0 ]; do
|
|
2015
2163
|
case "$1" in
|
|
2016
|
-
--args)
|
|
2017
|
-
--ecosystem)
|
|
2018
|
-
--help|-h) echo "foundation-list [--
|
|
2164
|
+
--args|--kind|--profile|--lane-id) fwd+=("$1" "$2"); shift 2 ;;
|
|
2165
|
+
--ecosystem) fwd+=(--ecosystem "$2"); eco_given=true; shift 2 ;;
|
|
2166
|
+
--help|-h) echo "foundation-list [--kind backend|frontend] [--profile <id>] [--ecosystem <eco>] [--lane-id <id>] [--args '<Foundation> <App>'] -> {options[], entries[], count, foundations[], specs[], all_foundations[], preselect_option, preselect_name, preselect_app, rejected_out}"; return 0 ;;
|
|
2019
2167
|
*) echo "foundation-list: unknown arg '$1'" >&2; return 1 ;;
|
|
2020
2168
|
esac
|
|
2021
2169
|
done
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
# compiling project in one ecosystem's languages and build system, so unpacking
|
|
2026
|
-
# a .NET one into a Spring repo does not produce a scaffold — it produces a
|
|
2027
|
-
# second, foreign project beside the real one.
|
|
2028
|
-
#
|
|
2029
|
-
# --ecosystem narrows to the ONE lane being scaffolded, which is stricter than the
|
|
2030
|
-
# union of every declared lane: a repo with a .NET service and a Node UI must not be
|
|
2031
|
-
# offered its .NET bundle while the UI lane is the target. Without it the filter falls
|
|
2032
|
-
# back to every declared lane's ecosystem, and to nothing at all on a bare repo.
|
|
2033
|
-
local -a lane_ecos=()
|
|
2034
|
-
local e
|
|
2035
|
-
if [ -n "$eco_filter" ]; then
|
|
2036
|
-
lane_ecos=("$eco_filter")
|
|
2037
|
-
else
|
|
2038
|
-
while IFS= read -r e; do [ -n "$e" ] && lane_ecos+=("$e"); done < <(_aw_ecosystems)
|
|
2170
|
+
if [ "$eco_given" = false ]; then
|
|
2171
|
+
local e
|
|
2172
|
+
while IFS= read -r e; do [ -n "$e" ] && fwd+=(--ecosystem "$e"); done < <(_aw_ecosystems)
|
|
2039
2173
|
fi
|
|
2174
|
+
_aw_foundation_py list "${fwd[@]}"
|
|
2175
|
+
}
|
|
2040
2176
|
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
done
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2177
|
+
# ─────────────────────────── foundation-choice ─────────────────────────────
|
|
2178
|
+
# Resolve the option picked from foundation-list back to its entry: route (pack | spec |
|
|
2179
|
+
# manual | skip), name and file. Takes the same filters as the list, so a numbered answer
|
|
2180
|
+
# counts the same rows the operator saw.
|
|
2181
|
+
cmd_foundation_choice() {
|
|
2182
|
+
local -a fwd=()
|
|
2183
|
+
while [ $# -gt 0 ]; do
|
|
2184
|
+
case "$1" in
|
|
2185
|
+
--selection|--kind|--profile|--ecosystem) fwd+=("$1" "$2"); shift 2 ;;
|
|
2186
|
+
--help|-h) echo "foundation-choice --selection <option> [--kind <k>] [--profile <id>] [--ecosystem <eco>] -> {ok, route, name, file, ecosystem, title, reason}"; return 0 ;;
|
|
2187
|
+
*) echo "foundation-choice: unknown arg '$1'" >&2; return 1 ;;
|
|
2188
|
+
esac
|
|
2189
|
+
done
|
|
2190
|
+
_aw_foundation_py choice "${fwd[@]}"
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
# ────────────────────────── foundation-spec-capture ────────────────────────
|
|
2194
|
+
# Copy the spec a lane is about to be built from to .appweaver/foundation/<lane>.spec.md
|
|
2195
|
+
# — from a bundle's *.spec.txt (--file), or from what the operator gave (--input: a path
|
|
2196
|
+
# when it names a file, the spec text otherwise). Always exits 0; `ok` carries the verdict.
|
|
2197
|
+
# Also returns the lane settings the spec's header gives (contract_run, contract, client),
|
|
2198
|
+
# with `<App>` replaced by --app.
|
|
2199
|
+
cmd_foundation_spec_capture() {
|
|
2200
|
+
local -a fwd=()
|
|
2201
|
+
while [ $# -gt 0 ]; do
|
|
2202
|
+
case "$1" in
|
|
2203
|
+
--lane|--kind|--file|--input|--app) fwd+=("$1" "$2"); shift 2 ;;
|
|
2204
|
+
--help|-h) echo "foundation-spec-capture --lane <id> [--kind <k>] (--file <path> | --input <path|text>) [--app <App>] -> {ok, spec_path, source, chars, title, reason, contract_run, contract, client, settings_text}"; return 0 ;;
|
|
2205
|
+
*) echo "foundation-spec-capture: unknown arg '$1'" >&2; return 1 ;;
|
|
2206
|
+
esac
|
|
2207
|
+
done
|
|
2208
|
+
_aw_foundation_py spec-capture "${fwd[@]}"
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
# ─────────────────────────── foundation-lane-files ─────────────────────────
|
|
2212
|
+
# What a lane already holds: its lane root folder, its test path when that is another
|
|
2213
|
+
# subtree, and its build files where its commands run when that is outside the lane (the
|
|
2214
|
+
# profile's `detect` entries with no `/`). Build output and dependency directories are
|
|
2215
|
+
# named, not walked. Asked before anything is scaffolded into the lane.
|
|
2216
|
+
cmd_foundation_lane_files() {
|
|
2217
|
+
local -a fwd=()
|
|
2218
|
+
while [ $# -gt 0 ]; do
|
|
2219
|
+
case "$1" in
|
|
2220
|
+
--source|--test|--workdir|--profile) fwd+=("$1" "$2"); shift 2 ;;
|
|
2221
|
+
--help|-h) echo "foundation-lane-files --source <p> [--test <p>] [--workdir <p>] [--profile <id>] -> {has_files, count, sample[], generated[], root_files[], folders[], workdir, summary}"; return 0 ;;
|
|
2222
|
+
*) echo "foundation-lane-files: unknown arg '$1'" >&2; return 1 ;;
|
|
2223
|
+
esac
|
|
2224
|
+
done
|
|
2225
|
+
_aw_foundation_py lane-files "${fwd[@]}"
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
# ─────────────────────── foundation-snapshot / -outside ────────────────────
|
|
2229
|
+
# A spec build is told to write only inside its lane root folder; these check that it did.
|
|
2230
|
+
# `snapshot` records every file in the repo before the build; `outside` lists what was
|
|
2231
|
+
# created, changed or deleted anywhere else afterwards and, with --undo, removes what was
|
|
2232
|
+
# created. Build output and dependency folders are one entry each, not walked.
|
|
2233
|
+
cmd_foundation_snapshot() {
|
|
2234
|
+
local -a fwd=()
|
|
2235
|
+
while [ $# -gt 0 ]; do
|
|
2236
|
+
case "$1" in
|
|
2237
|
+
--out) fwd+=("$1" "$2"); shift 2 ;;
|
|
2238
|
+
--help|-h) echo "foundation-snapshot --out <repo-relative file> -> {ok, snapshot, entries}"; return 0 ;;
|
|
2239
|
+
*) echo "foundation-snapshot: unknown arg '$1'" >&2; return 1 ;;
|
|
2240
|
+
esac
|
|
2241
|
+
done
|
|
2242
|
+
_aw_foundation_py snapshot "${fwd[@]}"
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
cmd_foundation_outside() {
|
|
2246
|
+
local -a fwd=()
|
|
2247
|
+
while [ $# -gt 0 ]; do
|
|
2248
|
+
case "$1" in
|
|
2249
|
+
--snapshot|--lane) fwd+=("$1" "$2"); shift 2 ;;
|
|
2250
|
+
--undo) fwd+=("$1"); shift ;;
|
|
2251
|
+
--help|-h) echo "foundation-outside --snapshot <file> --lane <lane root folder> [--undo] -> {clean, created[], changed[], deleted[], removed[], summary}"; return 0 ;;
|
|
2252
|
+
*) echo "foundation-outside: unknown arg '$1'" >&2; return 1 ;;
|
|
2253
|
+
esac
|
|
2254
|
+
done
|
|
2255
|
+
_aw_foundation_py outside "${fwd[@]}"
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
_aw_foundation_py() {
|
|
2259
|
+
local repo_root; repo_root=$(get_repo_root)
|
|
2260
|
+
local core="$SCRIPT_DIR/../python/aw-foundation.py"
|
|
2261
|
+
if [ ! -f "$core" ]; then
|
|
2262
|
+
echo "aw-foundation: core not found at $core" >&2; return 1
|
|
2263
|
+
fi
|
|
2264
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
2265
|
+
echo "aw-foundation: python3 is required to list foundations" >&2; return 1
|
|
2060
2266
|
fi
|
|
2061
|
-
|
|
2062
|
-
local match="" f
|
|
2063
|
-
for f in "${foundations[@]}"; do [ "$f" = "$pre_name" ] && match="$pre_name"; done
|
|
2064
|
-
printf '{"foundations":%s,"count":%s,"all_foundations":%s,"all_count":%s,"ecosystems":%s,"lane_ecosystems":%s,"filtered":%s,"preselect_name":"%s","preselect_app":"%s","rejected_out":"%s"}\n' \
|
|
2065
|
-
"$(json_str_array "${foundations[@]}")" "${#foundations[@]}" \
|
|
2066
|
-
"$(json_str_array "${all[@]}")" "${#all[@]}" \
|
|
2067
|
-
"$(json_str_array "${eco_of[@]}")" "$(json_str_array "${lane_ecos[@]}")" \
|
|
2068
|
-
"$([ ${#lane_ecos[@]} -gt 0 ] && echo true || echo false)" \
|
|
2069
|
-
"$(json_escape "$match")" "$(json_escape "$pre_app")" "$(json_escape "$rejected_out")"
|
|
2267
|
+
python3 "$core" --repo "$repo_root" "$@"
|
|
2070
2268
|
}
|
|
2071
2269
|
|
|
2072
2270
|
# ─────────────────────────── foundation-locate ─────────────────────────────
|
|
2073
2271
|
# Parse/validate args, locate the pack + unpacker, read the pack header (bounded).
|
|
2074
2272
|
cmd_foundation_locate() {
|
|
2075
|
-
local name="" app="" out="."
|
|
2273
|
+
local name="" app="" out="."
|
|
2076
2274
|
while [ $# -gt 0 ]; do
|
|
2077
2275
|
case "$1" in
|
|
2078
2276
|
--name) name="$2"; shift 2 ;;
|
|
2079
2277
|
--app) app="$2"; shift 2 ;;
|
|
2080
2278
|
--out) out="$2"; shift 2 ;;
|
|
2081
|
-
--
|
|
2082
|
-
--help|-h) echo "foundation-locate (--args '<F> <A> [out]' | --name <F> --app <A> [--out <dir>])"; return 0 ;;
|
|
2279
|
+
--help|-h) echo "foundation-locate --name <F> --app <A> [--out <dir>]"; return 0 ;;
|
|
2083
2280
|
*) echo "foundation-locate: unknown arg '$1'" >&2; return 1 ;;
|
|
2084
2281
|
esac
|
|
2085
2282
|
done
|
|
2086
|
-
# positional parse: "<FoundationName> <AppName> [OutputDir]"
|
|
2087
|
-
if [ -n "$raw" ]; then
|
|
2088
|
-
# shellcheck disable=SC2086
|
|
2089
|
-
set -- $raw
|
|
2090
|
-
[ -n "$1" ] && name="$1"; [ -n "$2" ] && app="$2"; [ -n "$3" ] && out="$3"
|
|
2091
|
-
fi
|
|
2092
2283
|
local repo_root; repo_root=$(get_repo_root)
|
|
2093
2284
|
# available = every foundation dir (_foundation_dirs already requires a *.pack.txt)
|
|
2094
2285
|
local -a available=()
|
|
@@ -2098,7 +2289,7 @@ cmd_foundation_locate() {
|
|
|
2098
2289
|
done < <(_foundation_dirs "$repo_root")
|
|
2099
2290
|
_fnd_fail() { printf '{"ok":false,"reason":"%s","available":%s}\n' "$(json_escape "$1")" "$(json_str_array "${available[@]}")"; }
|
|
2100
2291
|
if [ -z "$name" ] || [ -z "$app" ]; then
|
|
2101
|
-
_fnd_fail "
|
|
2292
|
+
_fnd_fail "foundation-locate needs --name <FoundationName> and --app <AppName>"; return 0
|
|
2102
2293
|
fi
|
|
2103
2294
|
local stack; stack=$(_foundation_stack "$repo_root" "$name")
|
|
2104
2295
|
if [ -z "$stack" ]; then _fnd_fail "Foundation '$name' not found (expected foundations/<stack>/$name/)"; return 0; fi
|
|
@@ -2113,7 +2304,7 @@ cmd_foundation_locate() {
|
|
|
2113
2304
|
elif [ -f "$fdir/unpack-foundation.sh" ]; then
|
|
2114
2305
|
runner="sh"; unpacker="$rel/unpack-foundation.sh"
|
|
2115
2306
|
else
|
|
2116
|
-
|
|
2307
|
+
_fnd_fail "No unpacker found in $rel/ (unpack-foundation.ps1 or .sh)"; return 0
|
|
2117
2308
|
fi
|
|
2118
2309
|
# bounded header read (never load the pack body)
|
|
2119
2310
|
local header sentinel file_count
|
|
@@ -2122,17 +2313,21 @@ cmd_foundation_locate() {
|
|
|
2122
2313
|
file_count=$(printf '%s\n' "$header" | grep -iE 'file' | grep -oE '[0-9]+' | head -1)
|
|
2123
2314
|
[ -n "$file_count" ] || file_count="0"
|
|
2124
2315
|
|
|
2125
|
-
|
|
2316
|
+
# A reference sample is offered only when its pack is there to unpack.
|
|
2317
|
+
local has_reference=false
|
|
2318
|
+
[ -f "$fdir/$name.Reference.pack.txt" ] && has_reference=true
|
|
2319
|
+
|
|
2320
|
+
printf '{"ok":true,"reason":"","name":"%s","stack":"%s","app":"%s","out_dir":"%s","pack_file":"%s","unpacker":"%s","runner":"%s","sentinel":"%s","file_count":%s,"has_reference":%s,"available":%s}\n' \
|
|
2126
2321
|
"$(json_escape "$name")" "$(json_escape "$stack")" "$(json_escape "$app")" "$(json_escape "$out")" "$(json_escape "$rel/$name.pack.txt")" \
|
|
2127
|
-
"$(json_escape "$unpacker")" "$runner" "$(json_escape "$sentinel")" "$file_count" "$(json_str_array "${available[@]}")"
|
|
2322
|
+
"$(json_escape "$unpacker")" "$runner" "$(json_escape "$sentinel")" "$file_count" "$has_reference" "$(json_str_array "${available[@]}")"
|
|
2128
2323
|
}
|
|
2129
2324
|
|
|
2130
2325
|
# ─────────────────────────── foundation-unpack ─────────────────────────────
|
|
2131
2326
|
# Run the OS-selected unpacker for the foundation (or reference) pack.
|
|
2132
2327
|
#
|
|
2133
|
-
# --out is the LANE
|
|
2328
|
+
# --out is the LANE ROOT FOLDER, so every file a pack carries lands inside the subtree
|
|
2134
2329
|
# its lane owns. --test-path is the lane's other subtree: a pack that carries its own test
|
|
2135
|
-
# project puts it under the
|
|
2330
|
+
# project puts it under the lane root folder like everything else, and aw-foundation.py lifts it
|
|
2136
2331
|
# onto the path the lane actually keeps tests in. See that module for when it is a no-op.
|
|
2137
2332
|
cmd_foundation_unpack() {
|
|
2138
2333
|
local name="" app="" out="." dry=false force="no" reference=false test_path=""
|
|
@@ -2206,27 +2401,49 @@ cmd_foundation_unpack() {
|
|
|
2206
2401
|
"$(json_escape "$(printf '%s' "$outp" | tail -c 800)")"
|
|
2207
2402
|
}
|
|
2208
2403
|
|
|
2209
|
-
#
|
|
2210
|
-
#
|
|
2211
|
-
#
|
|
2212
|
-
|
|
2213
|
-
|
|
2404
|
+
# ───────────────────── foundation-rules / -rules-done ──────────────────────
|
|
2405
|
+
# A pack's rule file (<pack name>.rules.json beside it) names the files and folders that
|
|
2406
|
+
# are merged with, or replaced over, what the lane already holds; every other pack file is
|
|
2407
|
+
# written as packed. `rules` runs before an unpack: it lists the merges and puts the files
|
|
2408
|
+
# they merge with aside under --state; `rules-done` removes them once merged. Both
|
|
2409
|
+
# delegate to aw-foundation.py.
|
|
2410
|
+
cmd_foundation_rules() {
|
|
2411
|
+
local -a fwd=()
|
|
2214
2412
|
while [ $# -gt 0 ]; do
|
|
2215
2413
|
case "$1" in
|
|
2216
|
-
--out)
|
|
2217
|
-
--
|
|
2218
|
-
|
|
2414
|
+
--name|--app|--out|--state) fwd+=("$1" "$2"); shift 2 ;;
|
|
2415
|
+
--reference) fwd+=("$1"); shift ;;
|
|
2416
|
+
--help|-h) echo "foundation-rules --name <F> --app <A> --out <lane root folder> [--reference] [--state <folder>] -> {ok, reason, rules_file, rules, merges[], merge_count, stale[], note}"; return 0 ;;
|
|
2417
|
+
*) echo "foundation-rules: unknown arg '$1'" >&2; return 1 ;;
|
|
2219
2418
|
esac
|
|
2220
2419
|
done
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
local
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2420
|
+
_aw_foundation_py rules "${fwd[@]}"
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
cmd_foundation_rules_done() {
|
|
2424
|
+
local -a fwd=()
|
|
2425
|
+
while [ $# -gt 0 ]; do
|
|
2426
|
+
case "$1" in
|
|
2427
|
+
--state) fwd+=("$1" "$2"); shift 2 ;;
|
|
2428
|
+
--help|-h) echo "foundation-rules-done [--state <folder>] -> {ok, removed}"; return 0 ;;
|
|
2429
|
+
*) echo "foundation-rules-done: unknown arg '$1'" >&2; return 1 ;;
|
|
2430
|
+
esac
|
|
2431
|
+
done
|
|
2432
|
+
_aw_foundation_py rules-done "${fwd[@]}"
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
# A pack's lane settings (<pack name>.lane.json beside it), sentinel renamed — what
|
|
2436
|
+
# foundation records on the lane with lane-set once the pack is unpacked.
|
|
2437
|
+
cmd_foundation_pack_lane() {
|
|
2438
|
+
local -a fwd=()
|
|
2439
|
+
while [ $# -gt 0 ]; do
|
|
2440
|
+
case "$1" in
|
|
2441
|
+
--name|--app) fwd+=("$1" "$2"); shift 2 ;;
|
|
2442
|
+
--help|-h) echo "foundation-pack-lane --name <F> --app <A> -> {found, file, contract_run, contract, client, reason}"; return 0 ;;
|
|
2443
|
+
*) echo "foundation-pack-lane: unknown arg '$1'" >&2; return 1 ;;
|
|
2444
|
+
esac
|
|
2445
|
+
done
|
|
2446
|
+
_aw_foundation_py pack-lane "${fwd[@]}"
|
|
2230
2447
|
}
|
|
2231
2448
|
|
|
2232
2449
|
# ─────────────────────────── foundation-verify ─────────────────────────────
|
|
@@ -2246,9 +2463,11 @@ cmd_foundation_verify() {
|
|
|
2246
2463
|
local -a residue=()
|
|
2247
2464
|
if [ -n "$sentinel" ] && [ -d "$out" ]; then
|
|
2248
2465
|
# scan the generated tree only — never the bundle/framework dirs (the
|
|
2249
|
-
# .pack.txt legitimately contains the sentinel).
|
|
2466
|
+
# .pack.txt legitimately contains the sentinel). -F: the sentinel is literal
|
|
2467
|
+
# text, and as a pattern its dots match any character ("AppWeaver Foo" would
|
|
2468
|
+
# count as AppWeaver.Foo) — the PowerShell twin uses -SimpleMatch for the same.
|
|
2250
2469
|
while IFS= read -r m; do [ -n "$m" ] && residue+=("$m"); done < <(
|
|
2251
|
-
grep -
|
|
2470
|
+
grep -rIlF --exclude-dir=.git --exclude-dir=guardrails --exclude-dir=foundations --exclude-dir=.appweaver \
|
|
2252
2471
|
--exclude-dir=specs --exclude-dir=node_modules -- "$sentinel" "$out" 2>/dev/null \
|
|
2253
2472
|
| sed "s#^$repo_root/##" | head -50 )
|
|
2254
2473
|
fi
|
|
@@ -2743,7 +2962,7 @@ cmd_find_host_project() {
|
|
|
2743
2962
|
done
|
|
2744
2963
|
local repo_root; repo_root=$(get_repo_root)
|
|
2745
2964
|
local prog
|
|
2746
|
-
# WHERE the host lives. The
|
|
2965
|
+
# WHERE the host lives. The declared lane root folder of the service lane when there is one,
|
|
2747
2966
|
# `src/` when there is not — the same question run-migrate asks, and the same answer.
|
|
2748
2967
|
# A repo whose service sits in `backend/` or `Legacy/App/` is searched where its code
|
|
2749
2968
|
# actually is instead of being reported as having no host project at all.
|
|
@@ -3061,10 +3280,15 @@ except Exception:
|
|
|
3061
3280
|
}
|
|
3062
3281
|
|
|
3063
3282
|
cmd_build_compile() {
|
|
3283
|
+
# --kind builds ONE declared lane — what `foundation` uses to check the lane it just
|
|
3284
|
+
# created without compiling a lane it did not touch. Ignored on the undeclared ladder
|
|
3285
|
+
# below, which has no lanes to choose between.
|
|
3286
|
+
local kind=""
|
|
3064
3287
|
while [ $# -gt 0 ]; do
|
|
3065
3288
|
case "$1" in
|
|
3066
3289
|
--feature-dir) shift 2 ;;
|
|
3067
|
-
--
|
|
3290
|
+
--kind) kind="$2"; shift 2 ;;
|
|
3291
|
+
--help|-h) echo "build-compile [--feature-dir <path>] [--kind backend|frontend]"; return 0 ;;
|
|
3068
3292
|
*) echo "build-compile: unknown arg '$1'" >&2; return 1 ;;
|
|
3069
3293
|
esac
|
|
3070
3294
|
done
|
|
@@ -3074,7 +3298,11 @@ cmd_build_compile() {
|
|
|
3074
3298
|
# sniffer takes the first toolchain it finds and silently never builds the
|
|
3075
3299
|
# second, which reports green for a frontend that was never compiled.
|
|
3076
3300
|
if [ -n "$(_aw_lanes_declared)" ]; then
|
|
3077
|
-
|
|
3301
|
+
if [ -n "$kind" ]; then
|
|
3302
|
+
_aw_stack_py run-phase --phase build --kind "$kind"
|
|
3303
|
+
else
|
|
3304
|
+
_aw_stack_py run-phase --phase build
|
|
3305
|
+
fi
|
|
3078
3306
|
return $?
|
|
3079
3307
|
fi
|
|
3080
3308
|
|
|
@@ -3283,8 +3511,8 @@ print(json.dumps({"ok": bool(d.get("ok")), "tool": "lanes", "ran": bool(d.get("r
|
|
|
3283
3511
|
return 0
|
|
3284
3512
|
fi
|
|
3285
3513
|
|
|
3286
|
-
# WHERE to look for the migration project. The
|
|
3287
|
-
#
|
|
3514
|
+
# WHERE to look for the migration project. The declared lane root folder of the
|
|
3515
|
+
# service lane when there is one, `src/` when there is not. Migrations belong to the
|
|
3288
3516
|
# backend lane by definition, so a repo whose service code sits in `Legacy/App/`
|
|
3289
3517
|
# or `backend/` gets searched where its code actually is instead of being told
|
|
3290
3518
|
# it has no .NET project at all.
|
|
@@ -3297,7 +3525,7 @@ print(json.dumps({"ok": bool(d.get("ok")), "tool": "lanes", "ran": bool(d.get("r
|
|
|
3297
3525
|
if ls "$repo_root"/*.sln >/dev/null 2>&1 || find "$svc_dir" -maxdepth 4 -name "*.csproj" 2>/dev/null | grep -q .; then has_dotnet=true; fi
|
|
3298
3526
|
if [ "$has_dotnet" = true ] && command -v dotnet >/dev/null 2>&1 && ( cd "$repo_root" && dotnet ef --version >/dev/null 2>&1 ); then
|
|
3299
3527
|
# Prefer *Service.csproj (API host with ApplicationDbContext); fall back to first
|
|
3300
|
-
# non-AppHost/Web/ServiceDefaults/test project under the service lane
|
|
3528
|
+
# non-AppHost/Web/ServiceDefaults/test project under the service lane root folder.
|
|
3301
3529
|
local project=""
|
|
3302
3530
|
project=$(find "$svc_dir" -maxdepth 3 -name '*.Service.csproj' 2>/dev/null | head -1)
|
|
3303
3531
|
if [ -z "$project" ]; then
|
|
@@ -5051,10 +5279,15 @@ case "$sub" in
|
|
|
5051
5279
|
lane-resolve) cmd_lane_resolve "$@" ;;
|
|
5052
5280
|
lane-verify) cmd_lane_verify "$@" ;;
|
|
5053
5281
|
lane-probe) cmd_lane_probe "$@" ;;
|
|
5282
|
+
lane-set) cmd_lane_set "$@" ;;
|
|
5283
|
+
contract-export) cmd_contract_export "$@" ;;
|
|
5284
|
+
contract-client) cmd_contract_client "$@" ;;
|
|
5285
|
+
contract-check) cmd_contract_check "$@" ;;
|
|
5054
5286
|
list-contexts) cmd_list_contexts "$@" ;;
|
|
5055
5287
|
set-context) cmd_set_context "$@" ;;
|
|
5056
5288
|
list-features) cmd_list_features "$@" ;;
|
|
5057
5289
|
set-feature) cmd_set_feature "$@" ;;
|
|
5290
|
+
default-scope) cmd_default_scope "$@" ;;
|
|
5058
5291
|
version-read) cmd_version_read "$@" ;;
|
|
5059
5292
|
version-init) cmd_version_init "$@" ;;
|
|
5060
5293
|
version-bootstrap-done) cmd_version_bootstrap_done "$@" ;;
|
|
@@ -5095,10 +5328,17 @@ case "$sub" in
|
|
|
5095
5328
|
checklist-apply) cmd_checklist_apply "$@" ;;
|
|
5096
5329
|
detect-project) cmd_detect_project "$@" ;;
|
|
5097
5330
|
foundation-list) cmd_foundation_list "$@" ;;
|
|
5331
|
+
foundation-choice) cmd_foundation_choice "$@" ;;
|
|
5332
|
+
foundation-spec-capture) cmd_foundation_spec_capture "$@" ;;
|
|
5333
|
+
foundation-lane-files) cmd_foundation_lane_files "$@" ;;
|
|
5334
|
+
foundation-snapshot) cmd_foundation_snapshot "$@" ;;
|
|
5335
|
+
foundation-outside) cmd_foundation_outside "$@" ;;
|
|
5098
5336
|
foundation-locate) cmd_foundation_locate "$@" ;;
|
|
5099
5337
|
foundation-unpack) cmd_foundation_unpack "$@" ;;
|
|
5100
5338
|
foundation-verify) cmd_foundation_verify "$@" ;;
|
|
5101
|
-
foundation-
|
|
5339
|
+
foundation-rules) cmd_foundation_rules "$@" ;;
|
|
5340
|
+
foundation-rules-done) cmd_foundation_rules_done "$@" ;;
|
|
5341
|
+
foundation-pack-lane) cmd_foundation_pack_lane "$@" ;;
|
|
5102
5342
|
list-components) cmd_list_components "$@" ;;
|
|
5103
5343
|
component-catalog) cmd_component_catalog "$@" ;;
|
|
5104
5344
|
component-verify) cmd_component_verify "$@" ;;
|