aw-spec-cli 0.1.18 → 0.1.20
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/.appweaver/DESIGN-FORMAT.md +63 -31
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +10 -6
- package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +9 -1
- package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +21 -0
- package/assets/.appweaver/orchestrator/orchestrator.py +23 -1
- package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -13
- package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +1 -1
- package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +6 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +399 -131
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +199 -38
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +159 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +20 -9
- package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +183 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +311 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +221 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +217 -174
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +494 -109
- package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +83 -18
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +1 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +29 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +143 -34
- package/assets/.appweaver/scripts/bash/aw-inventory.sh +15 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +878 -51
- package/assets/.appweaver/scripts/bash/aw-xref.sh +3 -0
- package/assets/.appweaver/scripts/bash/common.sh +28 -1
- package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +15 -0
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +868 -51
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +3 -0
- package/assets/.appweaver/scripts/python/aw-inventory.py +1832 -65
- package/assets/.appweaver/scripts/python/aw-xref.py +500 -81
- package/assets/.appweaver/templates/delta-design-template.md +1 -1
- package/assets/.appweaver/templates/delta-domain-model-template.md +1 -1
- package/assets/.appweaver/templates/delta-spec-template.md +4 -3
- package/assets/.appweaver/templates/design-template.md +56 -40
- package/assets/.appweaver/templates/okf-concept-template.md +11 -1
- package/assets/.appweaver/templates/proposed-inventory-template.md +127 -0
- package/assets/.appweaver/templates/source-map-template.md +5 -3
- package/assets/.appweaver/templates/spec-template.md +24 -21
- package/assets/.appweaver/templates/ui-delta-design-template.md +1 -1
- package/assets/.appweaver/templates/ui-design-template.md +11 -7
- package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +4489 -361
- package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2274 -125
- package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
- package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
- 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/cqrs.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/data-classification.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/modular-monolith.md +1 -1
- package/assets/guardrails/technology/dotnet-webapi/persistence.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-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/component-architecture.md +1 -1
- 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/i18n.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/package.json +1 -1
|
@@ -95,7 +95,7 @@ workflow:
|
|
|
95
95
|
action: verify_context
|
|
96
96
|
require_non_empty: feature_dir
|
|
97
97
|
on_empty: stop
|
|
98
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
98
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run parity."
|
|
99
99
|
|
|
100
100
|
- id: verify_spec
|
|
101
101
|
action: verify_context
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
|
|
86
86
|
name: aw-spec-plan
|
|
87
87
|
description: "Author the feature's lane plans from the spec and the bounded context's domain model — backend-plan.md when a backend lane is declared, frontend-plan.md when a frontend lane is, both when both are. Lanes come from .appweaver/stacks.json, so one command covers backend-only, frontend-only and full-stack projects; the former separate design-ui command is folded in. It ASKS what it is planning, the way implement asks what it is building: the master (`main`), one of the OPEN CHANGES, or a new plan-only change for a refactor with no spec delta — the argument doubles as the answer, and the menu appears only once a master exists, so a first run bootstraps with no prompt. `main` on a feature that already has masters RE-DERIVES them from the merged spec.md + domain-model.md — the operation finalize-delta's contract assumes when it merges a change's spec and model halves and no lane plan — and is confirmed before it rewrites anything. A change instead gets delta-backend-plan.md / delta-frontend-plan.md inside changes/<CR-id>/ while the masters stay untouched, written against the effective model (the master model, the change's own model delta, and the pending model deltas of LOWER waves, which are its predecessors) and against the feature's real source files from its OKF concept. Master authoring is template-driven: the kit's templates by default, or the code-derived as-built structure when onboard installed an override. Authoring only — a master gates at analyze-plan; a change ships through the CR chain."
|
|
88
|
-
version: 5.
|
|
88
|
+
version: 5.1.0
|
|
89
89
|
agent_agnostic: true
|
|
90
90
|
dialect: aw-spec.orchestrator/v1
|
|
91
91
|
|
|
@@ -164,6 +164,10 @@ vars:
|
|
|
164
164
|
plan_mode: "bootstrap" # bootstrap | rederive | delta — reported, not branched on
|
|
165
165
|
domain_model: ""
|
|
166
166
|
domain_model_exists: "false"
|
|
167
|
+
owned_sections: "" # the model section(s) this feature answers for — feature-aggregates
|
|
168
|
+
shared_with_csv: "" # the other features answering for the same aggregate(s)
|
|
169
|
+
one_to_one: "true"
|
|
170
|
+
model_scope_plan: "" # which part of those sections the domain layer realises
|
|
167
171
|
source_files: [] # the feature's real source files, from its OKF concept
|
|
168
172
|
source_count: "0"
|
|
169
173
|
|
|
@@ -200,18 +204,21 @@ workflow:
|
|
|
200
204
|
# project's standards are exactly what they were before the two commands merged.
|
|
201
205
|
# No blocking gate: an unconfigured project soft-continues (the constitutions still
|
|
202
206
|
# carry each lane's ratified base).
|
|
207
|
+
#
|
|
208
|
+
# ONLY THE BACKEND'S RULES LOAD HERE. The frontend's used to load alongside them,
|
|
209
|
+
# before either lane authored anything, and sticky standards only accumulate — so the
|
|
210
|
+
# service plan was authored with `## /aw-spec-plan-ui` in its envelope and the
|
|
211
|
+
# experience plan with the service set. Each prompt names its lane, but it said so
|
|
212
|
+
# inside an envelope that contradicted it. The UI set now loads at the frontend
|
|
213
|
+
# boundary instead, with `replace`, in both places a frontend document is authored:
|
|
214
|
+
# tech_select_ui_master before the master frontend sections, and the delta-plan
|
|
215
|
+
# module's own swap before the frontend delta plan.
|
|
203
216
|
- id: tech_select
|
|
204
217
|
condition: "{{be_declared}} == true"
|
|
205
218
|
action: include
|
|
206
219
|
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
207
220
|
with: { command: aw-spec-plan }
|
|
208
221
|
|
|
209
|
-
- id: tech_select_ui
|
|
210
|
-
condition: "{{fe_declared}} == true"
|
|
211
|
-
action: include
|
|
212
|
-
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
213
|
-
with: { command: aw-spec-plan-ui }
|
|
214
|
-
|
|
215
222
|
# 2. Resolve the current feature and require its spec.
|
|
216
223
|
- id: feature_paths
|
|
217
224
|
action: execute_script
|
|
@@ -238,11 +245,42 @@ workflow:
|
|
|
238
245
|
- { name: domain_model, from: "$.domain_model" }
|
|
239
246
|
- { name: domain_model_exists, from: "$.domain_model_exists" }
|
|
240
247
|
|
|
248
|
+
# WHICH PART OF THAT MODEL THIS PLAN REALISES. The model is filed by aggregate, and a
|
|
249
|
+
# feature is a capability scope the developer chose: it may act on several aggregates,
|
|
250
|
+
# and several features may act on one. Resolved from the spec's Acts-on line, then the
|
|
251
|
+
# boundary map, then the feature name. on_error: continue — without an answer the
|
|
252
|
+
# prompt falls back to the model as a whole, which is what it read before.
|
|
253
|
+
- id: feature_aggregates
|
|
254
|
+
action: execute_script
|
|
255
|
+
on_error: continue
|
|
256
|
+
script: { ref: xref }
|
|
257
|
+
subcommand: feature-aggregates
|
|
258
|
+
args: ["--feature-dir", "{{feature_dir}}"]
|
|
259
|
+
outputs:
|
|
260
|
+
- { name: owned_sections, from: "$.owned_sections" }
|
|
261
|
+
- { name: shared_with_csv, from: "$.shared_with_csv" }
|
|
262
|
+
- { name: one_to_one, from: "$.one_to_one" }
|
|
263
|
+
|
|
264
|
+
# HOW MUCH OF THOSE SECTIONS THE DOMAIN LAYER CARRIES. A feature that alone answers for
|
|
265
|
+
# an aggregate realises its whole section. A feature that shares it realises only what
|
|
266
|
+
# its own requirements act on: the rest is a sibling feature's, and planning it here
|
|
267
|
+
# would give two plans the same element to realise. The engine has no conditional
|
|
268
|
+
# prompt text, so the rule is an ordered pair of sets handed to the prompt as one line.
|
|
269
|
+
- id: model_scope_shared
|
|
270
|
+
action: set
|
|
271
|
+
vars:
|
|
272
|
+
model_scope_plan: "{{owned_sections}}, which this feature SHARES with other features. Realise only the entities, value objects, states and BR-nnn this feature's requirements act on, under the model's names and boundary; an element of that section this feature does not use belongs to another feature and is left out — never redefined, renamed or narrowed"
|
|
273
|
+
- id: model_scope_sole
|
|
274
|
+
condition: "{{one_to_one}} == true"
|
|
275
|
+
action: set
|
|
276
|
+
vars:
|
|
277
|
+
model_scope_plan: "{{owned_sections}}. This feature is the only one that answers for it, so the domain layer realises that whole section"
|
|
278
|
+
|
|
241
279
|
- id: require_feature
|
|
242
280
|
action: verify_context
|
|
243
281
|
require_non_empty: feature_dir
|
|
244
282
|
on_empty: stop
|
|
245
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
283
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run plan."
|
|
246
284
|
|
|
247
285
|
- id: verify_spec
|
|
248
286
|
action: verify_context
|
|
@@ -528,9 +566,10 @@ workflow:
|
|
|
528
566
|
- THE SPEC AND THE DOMAIN MODEL ARE THE ONLY DOMAIN INPUTS. You have them plus the
|
|
529
567
|
technical standards, and NO domain rule files. Realise what they state, and invent
|
|
530
568
|
no domain rule they omit (an unavoidable new concept → §7 DA-n).
|
|
531
|
-
- THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY
|
|
532
|
-
|
|
533
|
-
|
|
569
|
+
- THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY. The
|
|
570
|
+
section(s) this feature answers for: {{model_scope_plan}}. Within that scope,
|
|
571
|
+
entities & value objects are the section's entities (SAME names), invariants are
|
|
572
|
+
its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
|
|
534
573
|
block, and states are the lifecycle it describes. The model — not the spec — is
|
|
535
574
|
where those facts live; the spec cites them by id. This document records WHERE
|
|
536
575
|
each invariant is ENFORCED, never what it says. Elaborate with domain events /
|
|
@@ -628,10 +667,11 @@ workflow:
|
|
|
628
667
|
textbook layer gets its own section; a layer the feature does not need does NOT
|
|
629
668
|
appear.
|
|
630
669
|
- KEEP THE NEUTRAL ANCHORS (the template pins them): §1.2 is the domain/data MODEL
|
|
631
|
-
and MUST follow the CONTEXT'S DOMAIN MODEL EXACTLY
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
670
|
+
and MUST follow the CONTEXT'S DOMAIN MODEL EXACTLY for the section(s) this feature
|
|
671
|
+
answers for — {{model_scope_plan}}. Entities & value objects are the section's
|
|
672
|
+
entities (SAME names), invariants are its BR-nnn (cite each and record WHERE each
|
|
673
|
+
is enforced), the boundary is its Boundary block, states are its lifecycle;
|
|
674
|
+
cross-slice references-by-id live here. One §2
|
|
635
675
|
subsection is the EXTERNAL INTEGRATIONS table (its heading contains "Integration")
|
|
636
676
|
— one port/adapter row per IC-, realising the obligation the spec named.
|
|
637
677
|
- REALISE THE LIFTED CONSTRAINTS (each cites its spec id): CN- → structural ones
|
|
@@ -701,9 +741,10 @@ workflow:
|
|
|
701
741
|
A row whose id is gone is deleted along with what it justified. A spec id or model
|
|
702
742
|
invariant with NO plan element is a GAP — realise it, in the shape the rest of the
|
|
703
743
|
document already uses.
|
|
704
|
-
- THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY
|
|
705
|
-
|
|
706
|
-
|
|
744
|
+
- THE DOMAIN LAYER (§1) MUST FOLLOW THE CONTEXT'S DOMAIN MODEL EXACTLY. The
|
|
745
|
+
section(s) this feature answers for: {{model_scope_plan}}. Within that scope,
|
|
746
|
+
entities & value objects are the section's entities (SAME names), invariants are
|
|
747
|
+
its BR-nnn (cite each, never restate), the aggregate boundary is its Boundary
|
|
707
748
|
block, and states are the lifecycle it describes. Where the model has MOVED since
|
|
708
749
|
this plan was written, the model is right and the plan is stale.
|
|
709
750
|
- CARRY THE LEDGERS FORWARD. §7 DA-n architectural decisions and the WA-n
|
|
@@ -722,6 +763,23 @@ workflow:
|
|
|
722
763
|
(the spec ids and BR-nnn that drove it). Do NOT invent a version number — the
|
|
723
764
|
version spine belongs to close-delta.
|
|
724
765
|
|
|
766
|
+
# ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
|
|
767
|
+
# Both tech-select includes used to run up at step 4, before either lane authored
|
|
768
|
+
# anything, and sticky standards only accumulate — so on a full-stack run the backend
|
|
769
|
+
# plan was authored with `## /aw-spec-plan-ui` in its envelope and the frontend plan with
|
|
770
|
+
# the service set. Each prompt names its lane, but it did so inside an envelope that
|
|
771
|
+
# contradicted it, and the envelope is the half the model cannot argue with.
|
|
772
|
+
#
|
|
773
|
+
# `fe_mode != skip` is what makes this the MASTER path's swap and not the delta path's:
|
|
774
|
+
# fe_mode is only set to a real mode by the master branches above, so on a `plan CR-…`
|
|
775
|
+
# run this does not fire and the delta module below does its own swap at its own
|
|
776
|
+
# frontend boundary.
|
|
777
|
+
- id: tech_select_ui_master
|
|
778
|
+
condition: "{{fe_mode}} != skip"
|
|
779
|
+
action: include
|
|
780
|
+
module: .appweaver/orchestrator/modules/tech-select.orch.yaml
|
|
781
|
+
with: { command: aw-spec-plan-ui, replace: "true" }
|
|
782
|
+
|
|
725
783
|
# ═══════════ FRONTEND LANE — bootstrap, greenfield experience template ═══════
|
|
726
784
|
# The experience twin of the service plan, and the MEDIATOR between spec.md and the
|
|
727
785
|
# frontend code. Its sections are the experience, not the four clean-architecture
|
|
@@ -993,7 +1051,14 @@ workflow:
|
|
|
993
1051
|
- { name: wave, from: "$.wave" }
|
|
994
1052
|
- { name: predecessor_files, from: "$.predecessor_files" }
|
|
995
1053
|
- { name: predecessor_count, from: "$.predecessor_count" }
|
|
1054
|
+
- { name: predecessor_ids, from: "$.predecessor_ids" }
|
|
996
1055
|
- { name: sibling_count, from: "$.sibling_count" }
|
|
1056
|
+
- { name: sibling_ids, from: "$.sibling_ids" }
|
|
1057
|
+
# Predecessors AND siblings in one list: the delta plan is authored against both, and
|
|
1058
|
+
# a build_context takes a single `context_from:` variable. Which entries are which is
|
|
1059
|
+
# carried by predecessor_ids / sibling_ids, because the planner's licence over the two
|
|
1060
|
+
# is opposite — a predecessor may be built on, a sibling may only be designed around.
|
|
1061
|
+
- { name: related_docs, from: "$.related_docs" }
|
|
997
1062
|
|
|
998
1063
|
# Author that change's delta plans — one per declared lane whose MASTER exists, inside
|
|
999
1064
|
# changes/<CR-id>/ beside its delta-spec. The masters are left UNTOUCHED; the change
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# aw-spec-recall-delta (orchestrator) — WITHDRAW a change that never shipped
|
|
3
|
+
#
|
|
4
|
+
# The escape hatch of the CR loop, and the counterpart of close-delta:
|
|
5
|
+
# close-delta the change SHIPPED — archive it and bump the version
|
|
6
|
+
# recall-delta the change is WITHDRAWN — archive it and bump NOTHING
|
|
7
|
+
#
|
|
8
|
+
# specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta
|
|
9
|
+
# → finalize-delta → analyze-slice → close-delta
|
|
10
|
+
# └─ recall-delta may end it anywhere BEFORE finalize-delta ────────────┘
|
|
11
|
+
#
|
|
12
|
+
# WHY IT EXISTS. Changes get abandoned: the requirement was dropped, the approach was
|
|
13
|
+
# wrong, a wider refactor swallowed it. Until this stage there was no way to say so. The
|
|
14
|
+
# only mechanism that took a change out of the open set was `cr-archive`, which archives
|
|
15
|
+
# AND bumps the version in one act, so withdrawing one meant either burning a version
|
|
16
|
+
# number on work that shipped nothing or deleting the directory by hand and editing
|
|
17
|
+
# active_version.json — an operation with no record, no gate, and nothing to stop it being
|
|
18
|
+
# done to the wrong change. The archive and the bump are now separable (`cr-archive
|
|
19
|
+
# --recall`), and this stage is the supported way to reach that half.
|
|
20
|
+
#
|
|
21
|
+
# A RECALL IS NOT A ROLLBACK. It withdraws the change's DOCUMENTS — its delta-spec, its
|
|
22
|
+
# delta plans and its model delta, all archived intact under changes/archive/<id>/ beside a
|
|
23
|
+
# RECALLED.md that records the date and the reason. It does NOT revert code: a change that
|
|
24
|
+
# reached /appweaver implement has already generated source files, and reverting those is
|
|
25
|
+
# git's job, not this pipeline's. The output and the marker both say so, because a stage
|
|
26
|
+
# that half-undoes something while sounding like it undid all of it is worse than no stage.
|
|
27
|
+
#
|
|
28
|
+
# TWO REFUSALS, BOTH MECHANICAL AND BOTH READ OFF DISK.
|
|
29
|
+
#
|
|
30
|
+
# MERGED — finalize-delta has already folded this change into spec.md and the context's
|
|
31
|
+
# domain-model.md, and appended a Change History row carrying its id. The masters now
|
|
32
|
+
# describe behaviour that includes it, so withdrawing the change would leave them
|
|
33
|
+
# describing work no open change accounts for. The remedy for a merged change that should
|
|
34
|
+
# not have landed is a NEW change that reverses it. Detected from the id's presence in
|
|
35
|
+
# spec.md, which is that Change History row.
|
|
36
|
+
#
|
|
37
|
+
# ⏭️ THE UN-MERGE IS DELIBERATELY NOT BUILT (decision, 2026-09-11 — not a missing piece).
|
|
38
|
+
# Both prerequisites now exist: finalize-delta snapshots both masters to
|
|
39
|
+
# changes/<id>/pre-merge/, and cr-merge-check can prove no other delta landed in between,
|
|
40
|
+
# which is exactly the window a restore would be safe in. It is still not built, because a
|
|
41
|
+
# restore is a whole-file overwrite of two long-lived hand-curated documents, and what it
|
|
42
|
+
# would NOT roll back is the rest of what finalize-delta did — the OKF cross-reference
|
|
43
|
+
# edges and the re-authored concept Overview. A command that restores the masters while
|
|
44
|
+
# leaving the knowledge layer describing the withdrawn change is the kind of half-undo
|
|
45
|
+
# this stage's own header refuses to be. The snapshot is kept regardless: it is the record
|
|
46
|
+
# of what the masters said, and an operator who needs it can read it.
|
|
47
|
+
#
|
|
48
|
+
# DEPENDANTS — a wave is a dependency level. Same wave means independent; a HIGHER wave
|
|
49
|
+
# means planned and built on top of the lower ones. Withdrawing a lower-wave change
|
|
50
|
+
# therefore strands every open change above it, so this stage refuses and names them.
|
|
51
|
+
# `depends_on` is stamped nowhere yet (⌛ with the rest of the parallel-delta merge
|
|
52
|
+
# fence), so the wave is the only dependency signal that actually exists — and it is the
|
|
53
|
+
# conservative one, since same-wave siblings are never counted.
|
|
54
|
+
#
|
|
55
|
+
# THE VERSION DOES NOT MOVE. That is the entire point of the stage and the one invariant
|
|
56
|
+
# worth stating twice: a version records what a change SHIPPED as, and a recalled change
|
|
57
|
+
# shipped nothing. Nothing here stamps a header, appends a Change History row, or touches
|
|
58
|
+
# active_version.json's `version` field.
|
|
59
|
+
#
|
|
60
|
+
# Launch: /appweaver recall-delta [CR-id] (a bare id skips the prompt)
|
|
61
|
+
# Prereq: an OPEN change on the active feature that finalize-delta has NOT merged.
|
|
62
|
+
# =============================================================================
|
|
63
|
+
|
|
64
|
+
name: aw-spec-recall-delta
|
|
65
|
+
description: "Withdraw an open change that will not ship — archive changes/<id>/ and its model delta into changes/archive/<id>/ beside a RECALLED.md recording the date and reason, and drop it from active_version.json's open set WITHOUT bumping the version. Refuses a change finalize-delta already merged into the masters (the remedy there is a new change that reverses it) and one that a higher-wave open change is built on top of. Withdraws documents only — code already generated by implement is not reverted. Invokes no model and generates no code."
|
|
66
|
+
version: 1.0.0
|
|
67
|
+
agent_agnostic: true
|
|
68
|
+
dialect: aw-spec.orchestrator/v1
|
|
69
|
+
|
|
70
|
+
inputs:
|
|
71
|
+
user_input:
|
|
72
|
+
description: "Optional CR id of the change to withdraw. A bare id selects it and skips the prompt; anything else is ignored and the menu is shown."
|
|
73
|
+
required: false
|
|
74
|
+
bind: USER_INPUT
|
|
75
|
+
|
|
76
|
+
scripts:
|
|
77
|
+
actions:
|
|
78
|
+
sh: .appweaver/scripts/bash/aw-spec-actions.sh
|
|
79
|
+
ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
|
|
80
|
+
xref:
|
|
81
|
+
sh: .appweaver/scripts/bash/aw-xref.sh
|
|
82
|
+
ps1: .appweaver/scripts/powershell/aw-xref.ps1
|
|
83
|
+
|
|
84
|
+
# No standards seed and no domain/tech rule selection: this stage authors nothing, so it
|
|
85
|
+
# has no prompt to govern. It is bookkeeping over changes/ + active_version.json, plus the
|
|
86
|
+
# two refusals.
|
|
87
|
+
standards: []
|
|
88
|
+
|
|
89
|
+
vars:
|
|
90
|
+
has_changes: "false"
|
|
91
|
+
cr_choice: ""
|
|
92
|
+
cr_id: ""
|
|
93
|
+
merged: "false"
|
|
94
|
+
merge_evidence: ""
|
|
95
|
+
has_dependants: "false"
|
|
96
|
+
dependant_ids: ""
|
|
97
|
+
dependant_count: "0"
|
|
98
|
+
# The two refusal flags, both DOWN by default and raised only by the good state — see
|
|
99
|
+
# the gates below for why fail-closed is the right default here.
|
|
100
|
+
not_merged: "false"
|
|
101
|
+
no_dependants: "false"
|
|
102
|
+
recall_confirmed: "false"
|
|
103
|
+
recalled: "false"
|
|
104
|
+
archived_dir: ""
|
|
105
|
+
recall_marker: ""
|
|
106
|
+
open_count: "0"
|
|
107
|
+
|
|
108
|
+
workflow:
|
|
109
|
+
|
|
110
|
+
# 1. Resolve the active feature.
|
|
111
|
+
- id: feature_paths
|
|
112
|
+
action: execute_script
|
|
113
|
+
on_error: continue # never crash raw on 'no feature set' — the gate below explains it
|
|
114
|
+
script: { ref: actions }
|
|
115
|
+
subcommand: feature-paths
|
|
116
|
+
outputs:
|
|
117
|
+
- { name: feature, from: "$.feature" }
|
|
118
|
+
- { name: feature_dir, from: "$.feature_dir" }
|
|
119
|
+
|
|
120
|
+
- id: require_feature
|
|
121
|
+
action: verify_context
|
|
122
|
+
require_non_empty: feature_dir
|
|
123
|
+
on_empty: stop
|
|
124
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run recall-delta."
|
|
125
|
+
|
|
126
|
+
# 2. WHICH CHANGES ARE OPEN — the evidence is changes/<CR-id>/ on disk. No synthetic
|
|
127
|
+
# rows: `main` is not a change and there is nothing to open here, so the menu is the
|
|
128
|
+
# real changes and nothing else. `matched_label` resolves a CR id passed as the
|
|
129
|
+
# argument to the whole option string, which is how a bare `recall-delta CR-…` skips
|
|
130
|
+
# the prompt entirely.
|
|
131
|
+
- id: cr_list
|
|
132
|
+
action: execute_script
|
|
133
|
+
script: { ref: actions }
|
|
134
|
+
subcommand: cr-list
|
|
135
|
+
args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
|
|
136
|
+
outputs:
|
|
137
|
+
- { name: change_options, from: "$.select_options" }
|
|
138
|
+
- { name: matched_label, from: "$.matched_label" }
|
|
139
|
+
- { name: has_changes, from: "$.has_changes" }
|
|
140
|
+
- { name: change_count, from: "$.count" }
|
|
141
|
+
|
|
142
|
+
- id: require_changes
|
|
143
|
+
action: verify_context
|
|
144
|
+
require_true: has_changes
|
|
145
|
+
on_false: stop
|
|
146
|
+
on_false_message: "No open change on this feature — nothing to withdraw. A change is opened by /appweaver specify; one that already shipped was archived by /appweaver close-delta and is under changes/archive/, where a recall does not reach it."
|
|
147
|
+
|
|
148
|
+
# 3. WHICH ONE. single_option_confirm so a feature with exactly one open change still
|
|
149
|
+
# confirms the id out loud — this stage archives documents, and picking silently is
|
|
150
|
+
# how the wrong change gets withdrawn on a feature the operator has two open on.
|
|
151
|
+
- id: pick_change
|
|
152
|
+
action: user_select
|
|
153
|
+
prompt: "Which open change is being WITHDRAWN? Its delta-spec, delta plans and model delta are archived under changes/archive/ and it leaves the open set — with no version bump, because a recalled change ships nothing. Code that /appweaver implement already generated for it is NOT reverted; that is a git operation."
|
|
154
|
+
options_from_variable: change_options
|
|
155
|
+
preselect_from: matched_label
|
|
156
|
+
single_option_confirm: true
|
|
157
|
+
outputs:
|
|
158
|
+
- { name: cr_choice, from: selection }
|
|
159
|
+
|
|
160
|
+
# 3b. That change's documents, and the clean id back out of the label the pick returned.
|
|
161
|
+
- id: cr_paths
|
|
162
|
+
action: execute_script
|
|
163
|
+
script: { ref: actions }
|
|
164
|
+
subcommand: cr-paths
|
|
165
|
+
args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
|
|
166
|
+
outputs:
|
|
167
|
+
- { name: cr_id, from: "$.cr_id" }
|
|
168
|
+
- { name: cr_dir, from: "$.cr_dir" }
|
|
169
|
+
- { name: delta_spec, from: "$.delta_spec" }
|
|
170
|
+
- { name: delta_model, from: "$.delta_model" }
|
|
171
|
+
- { name: wave, from: "$.wave" }
|
|
172
|
+
|
|
173
|
+
# 4. THE TWO REFUSALS, both answered from disk in one call — see the header for why each
|
|
174
|
+
# one is a stop rather than a warning.
|
|
175
|
+
- id: recall_check
|
|
176
|
+
action: execute_script
|
|
177
|
+
script: { ref: actions }
|
|
178
|
+
subcommand: cr-recall-check
|
|
179
|
+
args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
|
|
180
|
+
outputs:
|
|
181
|
+
- { name: merged, from: "$.merged" }
|
|
182
|
+
- { name: merge_evidence, from: "$.merge_evidence" }
|
|
183
|
+
- { name: has_dependants, from: "$.has_dependants" }
|
|
184
|
+
- { name: dependant_ids, from: "$.dependant_ids" }
|
|
185
|
+
- { name: dependant_count, from: "$.dependant_count" }
|
|
186
|
+
|
|
187
|
+
# Each refusal is a RAISE then a gate, not a conditional gate: the raise fires only on
|
|
188
|
+
# the good state, so a value that never arrived (the script errored, the field was
|
|
189
|
+
# renamed) leaves the flag down and the stage refuses. Fail-closed is the only safe
|
|
190
|
+
# default for a gate in front of an archive.
|
|
191
|
+
- id: not_merged_raise
|
|
192
|
+
condition: "{{merged}} == false"
|
|
193
|
+
action: set
|
|
194
|
+
vars: { not_merged: "true" }
|
|
195
|
+
|
|
196
|
+
- id: refuse_merged
|
|
197
|
+
action: verify_context
|
|
198
|
+
require_true: not_merged
|
|
199
|
+
on_false: stop
|
|
200
|
+
on_false_message: "Change {{cr_id}} has already been MERGED — /appweaver finalize-delta folded it into spec.md and the context's domain-model.md, and the spec's Change History carries it: {{merge_evidence}}. The masters now describe behaviour that includes this change, so withdrawing it would leave them describing work no open change accounts for. A recall cannot un-merge, and this stage will not pretend otherwise. If the change should not have landed, finish it (/appweaver analyze-slice → close-delta) and then open a NEW change that reverses it with /appweaver specify — that keeps both the landing and the reversal in the ledger, which is what a reader of the spec needs to see."
|
|
201
|
+
|
|
202
|
+
- id: no_dependants_raise
|
|
203
|
+
condition: "{{has_dependants}} == false"
|
|
204
|
+
action: set
|
|
205
|
+
vars: { no_dependants: "true" }
|
|
206
|
+
|
|
207
|
+
- id: refuse_dependants
|
|
208
|
+
action: verify_context
|
|
209
|
+
require_true: no_dependants
|
|
210
|
+
on_false: stop
|
|
211
|
+
on_false_message: "Change {{cr_id}} is at wave {{wave}} and {{dependant_count}} open change(s) sit at a HIGHER wave: {{dependant_ids}}. A higher wave means planned and built ON TOP of the lower ones, so withdrawing this one strands them — their delta plans were authored against a predecessor that would no longer exist. Withdraw the higher-wave change(s) first (/appweaver recall-delta <CR-id>), or re-plan them against the lower waves that remain (/appweaver plan <CR-id>) before recalling this one. Same-wave changes are independent and are not counted here."
|
|
212
|
+
|
|
213
|
+
# 5. CONFIRM. Ordered no-first against the kit's usual yes-first convention, and
|
|
214
|
+
# deliberately: act_ask takes options[0] non-interactively, so an unattended run
|
|
215
|
+
# DECLINES rather than archiving a change nobody was watching. The reason is asked
|
|
216
|
+
# for in the same breath because it is what the marker records — a withdrawn change
|
|
217
|
+
# with no stated reason is a directory in an archive that nobody can account for later.
|
|
218
|
+
- id: confirm_recall
|
|
219
|
+
action: ask
|
|
220
|
+
prompt: "Withdraw {{cr_id}} (wave {{wave}})? Its documents — {{delta_spec}}, its delta plans and {{delta_model}} — move to changes/archive/{{cr_id}}/ and it leaves the open set. The version does NOT move: this change ships nothing. Any code implement already generated for it stays on disk; revert that with git if it should not remain. Answer no to stop with everything untouched."
|
|
221
|
+
options: ["no", "yes"]
|
|
222
|
+
answer_var: recall_answer
|
|
223
|
+
|
|
224
|
+
- id: recall_go
|
|
225
|
+
condition: "{{recall_answer}} == yes"
|
|
226
|
+
action: set
|
|
227
|
+
vars: { recall_confirmed: "true" }
|
|
228
|
+
|
|
229
|
+
- id: require_confirm
|
|
230
|
+
action: verify_context
|
|
231
|
+
require_true: recall_confirmed
|
|
232
|
+
on_false: stop
|
|
233
|
+
on_false_message: "Stopped — {{cr_id}} is untouched and still open. Nothing was archived and no version moved. Re-run and answer yes to withdraw it, or carry on with it: /appweaver plan {{cr_id}} to (re)author its delta plans, or /appweaver implement {{cr_id}} to build it."
|
|
234
|
+
|
|
235
|
+
- id: ask_reason
|
|
236
|
+
action: ask
|
|
237
|
+
prompt: "Why is {{cr_id}} being withdrawn? One line — it is written into changes/archive/{{cr_id}}/RECALLED.md, which is the only record of why this work stopped."
|
|
238
|
+
options: ["superseded by another change", "requirement dropped", "approach was wrong"]
|
|
239
|
+
answer_var: recall_reason
|
|
240
|
+
|
|
241
|
+
# 6. Pre-execution hooks.
|
|
242
|
+
- id: resolve_pre_hooks
|
|
243
|
+
action: execute_script
|
|
244
|
+
script: { ref: actions }
|
|
245
|
+
subcommand: resolve-hooks
|
|
246
|
+
args: ["--phase", "before_recall_cr"]
|
|
247
|
+
outputs:
|
|
248
|
+
- { name: before_hooks, from: "$.hooks" }
|
|
249
|
+
- id: dispatch_pre_hooks
|
|
250
|
+
action: dispatch_hooks
|
|
251
|
+
hooks_from_variable: before_hooks
|
|
252
|
+
|
|
253
|
+
# 7. THE RECALL. --recall is what separates this from close-delta: the same archive of
|
|
254
|
+
# the directory and of the model delta that lives beside the master, plus the
|
|
255
|
+
# RECALLED.md marker, and NO version bump. The script refuses an id the ledger does
|
|
256
|
+
# not carry and leaves everything in place when it does, so a refusal here is a no-op
|
|
257
|
+
# rather than a half-archived change.
|
|
258
|
+
- id: recall
|
|
259
|
+
action: execute_script
|
|
260
|
+
script: { ref: actions }
|
|
261
|
+
subcommand: cr-archive
|
|
262
|
+
args: ["--feature-dir", "{{feature_dir}}", "--cr-id", "{{cr_choice}}", "--recall", "--reason", "{{recall_reason}}"]
|
|
263
|
+
outputs:
|
|
264
|
+
- { name: recall_ok, from: "$.ok" }
|
|
265
|
+
- { name: recalled, from: "$.recalled" }
|
|
266
|
+
- { name: archived_dir, from: "$.archived_dir" }
|
|
267
|
+
- { name: archived_model, from: "$.archived_model" }
|
|
268
|
+
- { name: recall_marker, from: "$.recall_marker" }
|
|
269
|
+
- { name: version, from: "$.version" }
|
|
270
|
+
- { name: active_change, from: "$.active_change" }
|
|
271
|
+
- { name: open_count, from: "$.open_count" }
|
|
272
|
+
|
|
273
|
+
- id: verify_recalled
|
|
274
|
+
action: verify_context
|
|
275
|
+
require_true: recall_ok
|
|
276
|
+
on_false: stop
|
|
277
|
+
on_false_message: "The withdrawal of {{cr_id}} did not complete and nothing was moved — active_version.json does not carry it as an open change. That happens when the change was already closed or recalled, or when its directory exists on disk but was never registered by /appweaver specify. Check {{feature_dir}}/active_version.json against {{feature_dir}}/changes/."
|
|
278
|
+
|
|
279
|
+
# 8. Anchor the WITHDRAWAL in the OKF bundle's freshness journal (log.md, OKF §7).
|
|
280
|
+
# close-delta logs the ship; this logs the stop, which is exactly as much a part of
|
|
281
|
+
# the feature's history. Best-effort + git-aware: a no-op when there is no bundle.
|
|
282
|
+
- id: xref_log
|
|
283
|
+
action: execute_script
|
|
284
|
+
on_error: continue
|
|
285
|
+
script: { ref: xref }
|
|
286
|
+
subcommand: log
|
|
287
|
+
args: ["--note", "CR {{cr_id}} RECALLED — withdrawn before merge ({{recall_reason}}), archived to {{archived_dir}}, version unchanged at {{version}}"]
|
|
288
|
+
outputs:
|
|
289
|
+
- { name: xref_log, from: "$.log" }
|
|
290
|
+
|
|
291
|
+
# 9. Post-execution hooks.
|
|
292
|
+
- id: resolve_post_hooks
|
|
293
|
+
action: execute_script
|
|
294
|
+
script: { ref: actions }
|
|
295
|
+
subcommand: resolve-hooks
|
|
296
|
+
args: ["--phase", "after_recall_cr"]
|
|
297
|
+
outputs:
|
|
298
|
+
- { name: after_hooks, from: "$.hooks" }
|
|
299
|
+
- id: dispatch_post_hooks
|
|
300
|
+
action: dispatch_hooks
|
|
301
|
+
hooks_from_variable: after_hooks
|
|
302
|
+
|
|
303
|
+
outputs:
|
|
304
|
+
feature: "{{feature}}"
|
|
305
|
+
feature_dir: "{{feature_dir}}"
|
|
306
|
+
cr_id: "{{cr_id}}"
|
|
307
|
+
wave: "{{wave}}"
|
|
308
|
+
reason: "{{recall_reason}}"
|
|
309
|
+
archived_dir: "{{archived_dir}}"
|
|
310
|
+
archived_model: "{{archived_model}}"
|
|
311
|
+
recall_marker: "{{recall_marker}}"
|
|
312
|
+
# The number BEFORE and after are the same number — stated as an output because "nothing
|
|
313
|
+
# moved" is the result this stage exists to produce, and an absent field would read as
|
|
314
|
+
# an unanswered question rather than an answered one.
|
|
315
|
+
version: "{{version}}"
|
|
316
|
+
open_changes_remaining: "{{open_count}}"
|
|
317
|
+
active_change: "{{active_change}}"
|
|
318
|
+
note: "Change {{cr_id}} WITHDRAWN — its delta-spec, delta plans and model delta are archived intact at {{archived_dir}}, with the reason recorded in {{recall_marker}}. The version did NOT move and stands at {{version}}: this change shipped nothing, so no number was spent on it. spec.md and the context's domain-model.md never carried it and were not touched. Code that /appweaver implement generated for this change is still on disk — a recall withdraws documents, not source; revert it with git if it should not stay."
|
|
319
|
+
next: "{{open_count}} change(s) still open on this feature. Carry on with one of them (/appweaver plan <CR-id> → analyze-plan-delta → implement), or with none left the feature is back on its masters: /appweaver plan main re-derives the lane plans from spec.md and the domain model, and /appweaver specify opens a fresh change."
|
|
@@ -64,7 +64,7 @@ workflow:
|
|
|
64
64
|
action: verify_context
|
|
65
65
|
require_non_empty: feature_dir
|
|
66
66
|
on_empty: stop
|
|
67
|
-
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the
|
|
67
|
+
on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the feature, e.g. Order or PatientRegistration), then re-run run."
|
|
68
68
|
|
|
69
69
|
# Require the SERVICE LANE and its source path. Migrations belong to the backend
|
|
70
70
|
# lane by definition, and run-migrate searches that lane's root for the project
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
2
|
# aw-spec-select-feature (orchestrator) — pick or create the active FEATURE
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
4
|
+
# A FEATURE IS A CAPABILITY SCOPE THE DEVELOPER CHOOSES (e.g. Order, or
|
|
5
|
+
# PatientRegistration). It may act on several aggregates, and several features may act on
|
|
6
|
+
# one: the aggregates live in the bounded context's domain model and the OKF bundle, above
|
|
7
|
+
# every feature, and the spec names the ones this feature acts on. This is the FIRST step
|
|
8
|
+
# of the spec-anchored pipeline: it fixes the feature's scope BEFORE any spec/plan exists,
|
|
9
|
+
# so its single spec.md + backend-plan.md and every operation (CreateOrder/UpdateOrder =
|
|
10
|
+
# user stories) hang off one directory.
|
|
9
11
|
#
|
|
10
12
|
# The pickable list is TWO merged sources (deduped by name): specs/ sub-folders
|
|
11
|
-
# (features already scaffolded/adopted) AND the OKF bundle's
|
|
12
|
-
# the
|
|
13
|
-
# brownfield repo you pick a real discovered code
|
|
14
|
-
#
|
|
13
|
+
# (features already scaffolded/adopted) AND the features the OKF bundle's concepts are
|
|
14
|
+
# known to serve — from the boundary map the developer confirmed, else from each
|
|
15
|
+
# concept's `features:` list. So in a brownfield repo you pick a real discovered code
|
|
16
|
+
# feature BEFORE anything is under specs/. An aggregate is never offered as a feature
|
|
17
|
+
# nobody defined: on greenfield every feature already has its specs/ directory, so the
|
|
18
|
+
# list is specs-only.
|
|
15
19
|
#
|
|
16
20
|
# SCOPED BY THE ACTIVE BOUNDED CONTEXT. When .appweaver/context.json names one, both
|
|
17
21
|
# the listing and the creation happen inside specs/<Context>/ — so a context shows
|
|
@@ -22,7 +26,7 @@
|
|
|
22
26
|
# ZERO LLM: only scripts + one select + (conditionally) one ask. No generation,
|
|
23
27
|
# no standards, no domain-select. Pure, deterministic orchestration/governance.
|
|
24
28
|
#
|
|
25
|
-
# list-features (specs/ ∪ OKF
|
|
29
|
+
# list-features (specs/ ∪ features the OKF concepts serve) → user_select (existing | "+ create new") → [ask name] → set-feature
|
|
26
30
|
#
|
|
27
31
|
# Persists .appweaver/feature.json {"feature_directory":"specs/<Feature>"} so every
|
|
28
32
|
# later command operates on it: the forward loop (specify/plan/analyze-plan/implement/
|
|
@@ -36,14 +40,14 @@
|
|
|
36
40
|
# =============================================================================
|
|
37
41
|
|
|
38
42
|
name: aw-spec-select-feature
|
|
39
|
-
description: Pick an existing feature (
|
|
43
|
+
description: Pick an existing feature (a capability scope you choose) or create a new one, and store it in .appweaver/feature.json so every AW-Spec command operates on it. Zero LLM — scripts + one selection only.
|
|
40
44
|
version: 1.0.0
|
|
41
45
|
agent_agnostic: true
|
|
42
46
|
dialect: aw-spec.orchestrator/v1
|
|
43
47
|
|
|
44
48
|
inputs:
|
|
45
49
|
user_input:
|
|
46
|
-
description: Optional feature
|
|
50
|
+
description: Optional feature name to create or select directly, e.g. Order or PatientRegistration.
|
|
47
51
|
required: false
|
|
48
52
|
bind: USER_INPUT
|
|
49
53
|
|
|
@@ -85,11 +89,12 @@ workflow:
|
|
|
85
89
|
on_false_message: >
|
|
86
90
|
{{scope_message}}
|
|
87
91
|
|
|
88
|
-
# 1. Enumerate selectable
|
|
89
|
-
# specs/ sub-folders (already scaffolded/adopted) AND the OKF
|
|
90
|
-
#
|
|
91
|
-
# so a not-yet-adopted code feature is pickable before its
|
|
92
|
-
#
|
|
92
|
+
# 1. Enumerate selectable FEATURES from TWO merged sources (deduped by name):
|
|
93
|
+
# specs/ sub-folders (already scaffolded/adopted) AND the features the OKF
|
|
94
|
+
# bundle's aggregate concepts serve — the boundary map first, else each concept's
|
|
95
|
+
# `features:` list — so a not-yet-adopted code feature is pickable before its
|
|
96
|
+
# specs/ dir exists. A concept greenfield implement wrote contributes nothing:
|
|
97
|
+
# its features all have specs/ dirs, and its own name is an aggregate.
|
|
93
98
|
# `select_options` carries the "+ create new" sentinel and (when USER_INPUT
|
|
94
99
|
# names a not-yet feature) that name injected — a direct arg is one-list select.
|
|
95
100
|
- id: list_features
|
|
@@ -108,7 +113,7 @@ workflow:
|
|
|
108
113
|
# no prompt; the lone sentinel at bootstrap (no features yet) auto-confirms.
|
|
109
114
|
- id: pick
|
|
110
115
|
action: user_select
|
|
111
|
-
prompt: "Select a feature (
|
|
116
|
+
prompt: "Select a feature (a capability scope, e.g. Order or PatientRegistration) in {{feature_base}}/ — or create a new one:"
|
|
112
117
|
options_from_variable: select_options
|
|
113
118
|
preselect_from: USER_INPUT
|
|
114
119
|
single_option_confirm: true
|
|
@@ -120,9 +125,10 @@ workflow:
|
|
|
120
125
|
action: ask
|
|
121
126
|
condition: "{{chosen}} == {{new_sentinel}}"
|
|
122
127
|
prompt: >
|
|
123
|
-
Name the new feature —
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
Name the new feature — the capability scope you want to specify and build as
|
|
129
|
+
one unit (e.g. Order, PatientRegistration). It may act on several aggregates, and
|
|
130
|
+
other features may act on the same ones; its spec names them. Each operation in
|
|
131
|
+
its scope (e.g. CreateOrder / UpdateOrder) becomes a user story inside it:
|
|
126
132
|
answer_var: typed_name
|
|
127
133
|
|
|
128
134
|
# 4. Coalesce the target name. The engine has no compound conditions, so this
|
|
@@ -136,7 +142,7 @@ workflow:
|
|
|
136
142
|
condition: "{{chosen}} == {{new_sentinel}}"
|
|
137
143
|
vars: { target_name: "{{typed_name}}" }
|
|
138
144
|
|
|
139
|
-
# 5. Create-or-select the
|
|
145
|
+
# 5. Create-or-select the feature dir + persist it as the active feature.
|
|
140
146
|
- id: set_feature
|
|
141
147
|
action: execute_script
|
|
142
148
|
script: { ref: actions }
|
|
@@ -157,5 +163,5 @@ outputs:
|
|
|
157
163
|
context: "{{context}}"
|
|
158
164
|
created: "{{created}}"
|
|
159
165
|
spec_exists: "{{spec_exists}}"
|
|
160
|
-
note: "Active feature set in .appweaver/feature.json — the
|
|
166
|
+
note: "Active feature set in .appweaver/feature.json — the capability scope that owns its single spec.md + backend-plan.md. `context` names the bounded context it was created in, and is empty on a repo with no active context (the flat specs/<Feature> layout, which keeps working unchanged)."
|
|
161
167
|
next: "/appweaver specify <what this feature does> (authors {{feature_dir}}/spec.md; each operation = a user story)"
|