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
|
@@ -124,6 +124,15 @@ class Stop(Exception):
|
|
|
124
124
|
pass
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
class Return(Exception):
|
|
128
|
+
"""A gate's `return` outcome: end the module being included and carry on with the
|
|
129
|
+
step after the `include`. A Stop ends the whole skill, which is wrong for a caller
|
|
130
|
+
that runs one module per lane — the frontend lane must still run when the backend's
|
|
131
|
+
scaffold was declined. At the top level of a workflow it ends the run normally, with
|
|
132
|
+
the outputs resolved, rather than as a stop."""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
127
136
|
class Suspend(Exception):
|
|
128
137
|
"""Raised in checkpoint mode when a generation is needed: the interpreter
|
|
129
138
|
persists state and exits so the INVOKING agent can fulfill the request
|
|
@@ -231,9 +240,12 @@ class Orchestrator:
|
|
|
231
240
|
# paths an agent_generate reply named as written that were not on disk —
|
|
232
241
|
# run-cumulative, reported at the end beside missing_required.
|
|
233
242
|
self.reported_missing = []
|
|
243
|
+
# agent_author targets a --dry-run left unwritten; required_files gates
|
|
244
|
+
# count them as present, so a dry run reaches the steps after them.
|
|
245
|
+
self.dry_run_unwritten = []
|
|
234
246
|
self.last_result = {}
|
|
235
247
|
# agent-specific transport profile (None in mock mode). This engine is
|
|
236
|
-
# general — the profile (loaded from a .claude/.agents/.
|
|
248
|
+
# general — the profile (loaded from a .claude/.agents/.kilo file)
|
|
237
249
|
# describes HOW to spawn that agent's fresh generation CLI.
|
|
238
250
|
self.profile = getattr(opts, "profile", None)
|
|
239
251
|
# checkpoint transport (run-skill.py method): replay + response cache
|
|
@@ -545,7 +557,7 @@ class Orchestrator:
|
|
|
545
557
|
Returns (structured, raw).
|
|
546
558
|
|
|
547
559
|
The engine is agent-agnostic: the profile (agent-specific, from a
|
|
548
|
-
.claude/.agents/.
|
|
560
|
+
.claude/.agents/.kilo file) declares the CLI to spawn. A SEPARATE
|
|
549
561
|
process → a brand-new conversation that does NOT inherit this
|
|
550
562
|
orchestrator's transcript. The profile maps our neutral knobs
|
|
551
563
|
(system / user / tools / permission_mode / isolation / model / budget)
|
|
@@ -553,6 +565,34 @@ class Orchestrator:
|
|
|
553
565
|
if self.opts.transport == "mock":
|
|
554
566
|
# No model call. The banner (already printed) is the demonstration.
|
|
555
567
|
return None, None
|
|
568
|
+
argv, stdin_data = self._fresh_command(system, user, tools, permission_mode, model)
|
|
569
|
+
|
|
570
|
+
if self.opts.dry_run:
|
|
571
|
+
# Safety net: every model step prints its command through _mock_step and
|
|
572
|
+
# takes its mock result, so a dry run never reaches here — and must never
|
|
573
|
+
# execute if a new caller does.
|
|
574
|
+
self._emit_fresh_command(argv, stdin_data)
|
|
575
|
+
raise Stop("dry-run: command shown above (nothing executed)")
|
|
576
|
+
|
|
577
|
+
proc = subprocess.run(argv, input=stdin_data, capture_output=True, text=True)
|
|
578
|
+
if proc.returncode != 0:
|
|
579
|
+
raise Stop(f"fresh agent failed ({proc.returncode}): {proc.stderr.strip()[:500]}")
|
|
580
|
+
try:
|
|
581
|
+
obj = json.loads(proc.stdout)
|
|
582
|
+
except Exception:
|
|
583
|
+
return _extract_json(proc.stdout), proc.stdout # text-output fallback
|
|
584
|
+
raw = ""
|
|
585
|
+
for k in (self.profile or {}).get("result_keys", ["result"]):
|
|
586
|
+
if isinstance(obj, dict) and obj.get(k):
|
|
587
|
+
raw = obj[k]
|
|
588
|
+
break
|
|
589
|
+
if not raw:
|
|
590
|
+
raw = proc.stdout
|
|
591
|
+
return _extract_json(raw), raw
|
|
592
|
+
|
|
593
|
+
def _fresh_command(self, system, user, tools, permission_mode, model=None):
|
|
594
|
+
"""The argv (and stdin, or None) for one fresh generation, built from the
|
|
595
|
+
PROFILE's neutral knobs — shared by the real run and the dry-run print."""
|
|
556
596
|
prof = self.profile or {}
|
|
557
597
|
argv = [prof["bin"]] + list(prof.get("args", []))
|
|
558
598
|
model = model or self.opts.model # per-step `model:` beats --model
|
|
@@ -584,29 +624,32 @@ class Orchestrator:
|
|
|
584
624
|
argv += [umode.get("flag", "-p"), prompt_user]
|
|
585
625
|
else:
|
|
586
626
|
stdin_data = prompt_user
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if
|
|
608
|
-
|
|
609
|
-
|
|
627
|
+
return argv, stdin_data
|
|
628
|
+
|
|
629
|
+
def _emit_fresh_command(self, argv, stdin_data):
|
|
630
|
+
"""Print the command a dry run would have executed (long args elided)."""
|
|
631
|
+
shown = [a if len(a) < 120 else f"<{len(a)} chars>" for a in argv]
|
|
632
|
+
self.emit(f"[dry-run] {(self.profile or {}).get('name', '?')} would run:\n "
|
|
633
|
+
+ " ".join(shown))
|
|
634
|
+
if stdin_data is not None:
|
|
635
|
+
self.emit(f"[dry-run] + stdin: {len(stdin_data)} chars")
|
|
636
|
+
|
|
637
|
+
def _mock_step(self, strat, system, user, tools, permission_mode, model=None):
|
|
638
|
+
"""True when a model step takes its mock result instead of running: the
|
|
639
|
+
mock transport, or --dry-run under spawn/sdk/sdk-apply. A dry run first
|
|
640
|
+
prints the request the profile would send, then lets the step carry on
|
|
641
|
+
with its mock result, so one dry run prints every model step's command.
|
|
642
|
+
The agent strategy is left alone: the invoking agent fulfils it."""
|
|
643
|
+
if self.opts.transport == "mock":
|
|
644
|
+
return True
|
|
645
|
+
if not self.opts.dry_run or strat == "agent":
|
|
646
|
+
return False
|
|
647
|
+
if strat in ("sdk", "sdk-apply"):
|
|
648
|
+
self._emit_sdk_request(system, user, tools, permission_mode)
|
|
649
|
+
else:
|
|
650
|
+
self._emit_fresh_command(
|
|
651
|
+
*self._fresh_command(system, user, tools, permission_mode, model))
|
|
652
|
+
return True
|
|
610
653
|
|
|
611
654
|
def _ensure_sdk_module(self, module_name, package_name):
|
|
612
655
|
"""Import the profile-declared sdk module; if missing, optionally pip
|
|
@@ -663,6 +706,35 @@ class Orchestrator:
|
|
|
663
706
|
raise Stop(f"sdk adapter {path} must export generate(request)")
|
|
664
707
|
return fn
|
|
665
708
|
|
|
709
|
+
def _sdk_config(self):
|
|
710
|
+
"""(module, package, adapter) from the profile's sdk block — a dry run
|
|
711
|
+
checks them too, so it catches a profile that cannot run sdk/sdk-apply."""
|
|
712
|
+
if not self.profile:
|
|
713
|
+
raise Stop("strategy 'sdk'/'sdk-apply' needs --agent-profile — the "
|
|
714
|
+
"profile's sdk block declares the SDK to use (the engine "
|
|
715
|
+
"has no default agent)")
|
|
716
|
+
sdk_cfg = self.profile.get("sdk") or {}
|
|
717
|
+
module_name = sdk_cfg.get("module")
|
|
718
|
+
package_name = sdk_cfg.get("package")
|
|
719
|
+
adapter_rel = sdk_cfg.get("adapter")
|
|
720
|
+
missing = [k for k, v in (("module", module_name), ("package", package_name),
|
|
721
|
+
("adapter", adapter_rel)) if not v]
|
|
722
|
+
if missing:
|
|
723
|
+
raise Stop(f"agent profile '{self.profile.get('name', '?')}': sdk block "
|
|
724
|
+
f"is missing {', '.join(missing)} — strategy 'sdk'/'sdk-apply' "
|
|
725
|
+
"is fully profile-driven (no built-in default); declare the "
|
|
726
|
+
"sdk module/package to import and the adapter file that "
|
|
727
|
+
"drives it")
|
|
728
|
+
return module_name, package_name, adapter_rel
|
|
729
|
+
|
|
730
|
+
def _emit_sdk_request(self, system, user, tools, permission_mode):
|
|
731
|
+
"""Print the SDK request a dry run would have sent."""
|
|
732
|
+
module_name, package_name, adapter_rel = self._sdk_config()
|
|
733
|
+
self.emit(f"[dry-run] sdk would run: module={module_name} "
|
|
734
|
+
f"package={package_name} adapter={adapter_rel} "
|
|
735
|
+
f"system={len(system)} chars user={len(user)} chars "
|
|
736
|
+
f"tools={tools or '(none)'} permission_mode={permission_mode}")
|
|
737
|
+
|
|
666
738
|
def _run_sdk(self, system, user, tools, permission_mode, model=None):
|
|
667
739
|
"""Strategy 1/2 executor: an isolated generation via the agent's SDK.
|
|
668
740
|
Each call is a NEW session — the only context is the payload we pass
|
|
@@ -680,28 +752,12 @@ class Orchestrator:
|
|
|
680
752
|
SDK's calling convention (sync or async)
|
|
681
753
|
Everything is imported lazily so the engine stays stdlib-only
|
|
682
754
|
otherwise."""
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
"profile's sdk block declares the SDK to use (the engine "
|
|
686
|
-
"has no default agent)")
|
|
687
|
-
sdk_cfg = self.profile.get("sdk") or {}
|
|
688
|
-
module_name = sdk_cfg.get("module")
|
|
689
|
-
package_name = sdk_cfg.get("package")
|
|
690
|
-
adapter_rel = sdk_cfg.get("adapter")
|
|
691
|
-
missing = [k for k, v in (("module", module_name), ("package", package_name),
|
|
692
|
-
("adapter", adapter_rel)) if not v]
|
|
693
|
-
if missing:
|
|
694
|
-
raise Stop(f"agent profile '{self.profile.get('name', '?')}': sdk block "
|
|
695
|
-
f"is missing {', '.join(missing)} — strategy 'sdk'/'sdk-apply' "
|
|
696
|
-
"is fully profile-driven (no built-in default); declare the "
|
|
697
|
-
"sdk module/package to import and the adapter file that "
|
|
698
|
-
"drives it")
|
|
755
|
+
module_name, package_name, adapter_rel = self._sdk_config()
|
|
756
|
+
sdk_cfg = self.profile.get("sdk") or {} # handed to the adapter whole
|
|
699
757
|
|
|
700
758
|
if self.opts.dry_run:
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
f"system={len(system)} chars user={len(user)} chars "
|
|
704
|
-
f"tools={tools or '(none)'} permission_mode={permission_mode}")
|
|
759
|
+
# Safety net, as in _run_fresh: _mock_step prints the request first.
|
|
760
|
+
self._emit_sdk_request(system, user, tools, permission_mode)
|
|
705
761
|
raise Stop("dry-run: sdk request shown above (nothing executed)")
|
|
706
762
|
|
|
707
763
|
self._ensure_sdk_module(module_name, package_name)
|
|
@@ -839,7 +895,10 @@ class Orchestrator:
|
|
|
839
895
|
user, ctx_files = self._assemble_user(instr, extra=extra)
|
|
840
896
|
self._print_envelope("agent_generate", system, sys_files, user, ctx_files, tools, strat)
|
|
841
897
|
|
|
842
|
-
|
|
898
|
+
# sdk-apply's generation is the read-only one; the dry run shows that request
|
|
899
|
+
sent_tools, sent_mode = (("Read", "readonly") if strat == "sdk-apply"
|
|
900
|
+
else (tools, permission_mode))
|
|
901
|
+
if self._mock_step(strat, system, user, sent_tools, sent_mode, step.get("model")):
|
|
843
902
|
structured = self._mock_generate(task)
|
|
844
903
|
elif strat == "agent":
|
|
845
904
|
self._suspend("agent_generate", ck, system, user, tools, permission_mode)
|
|
@@ -853,7 +912,9 @@ class Orchestrator:
|
|
|
853
912
|
else: # spawn
|
|
854
913
|
structured, _ = self._run_fresh(system, user, tools, permission_mode,
|
|
855
914
|
model=step.get("model"))
|
|
856
|
-
|
|
915
|
+
# A dry run's result is a mock: caching it would let a later run without
|
|
916
|
+
# --dry-run replay it as if a model had produced it.
|
|
917
|
+
if self.checkpoint and not self.opts.dry_run:
|
|
857
918
|
self.cache[ck] = structured
|
|
858
919
|
structured = self._written_only(step, structured)
|
|
859
920
|
self.last_result = structured
|
|
@@ -893,7 +954,7 @@ class Orchestrator:
|
|
|
893
954
|
# No file tools by default: the files under review are already in the payload.
|
|
894
955
|
self._print_envelope("agent_review", system, sys_files, user, ctx_files, tools, strat)
|
|
895
956
|
|
|
896
|
-
if self.
|
|
957
|
+
if self._mock_step(strat, system, user, tools, "readonly", step.get("model")):
|
|
897
958
|
# mock: pass if every reviewed file exists
|
|
898
959
|
ok = all(os.path.exists(r) for r in review_of)
|
|
899
960
|
structured = {"pass": ok, "issues": ([] if ok else ["missing output file(s)"])}
|
|
@@ -907,7 +968,7 @@ class Orchestrator:
|
|
|
907
968
|
structured, _ = self._run_fresh(system, user, tools, "readonly",
|
|
908
969
|
model=step.get("model"))
|
|
909
970
|
self.last_result = structured
|
|
910
|
-
if self.checkpoint:
|
|
971
|
+
if self.checkpoint and not self.opts.dry_run: # never cache a dry run's mock
|
|
911
972
|
self.cache[ck] = structured
|
|
912
973
|
self._bind(step, structured)
|
|
913
974
|
self.emit(f"[agent_review] strategy={strat or 'mock'} "
|
|
@@ -988,10 +1049,23 @@ class Orchestrator:
|
|
|
988
1049
|
self.emit(f"[execute_script] -> {json.dumps(data)[:200]}")
|
|
989
1050
|
|
|
990
1051
|
# -- F2: gates ---------------------------------------------------------------
|
|
1052
|
+
def _gate_failed(self, p, outcome, msg):
|
|
1053
|
+
"""Apply a failed gate's outcome: `stop` ends the skill, `return` ends the module
|
|
1054
|
+
being included (see Return), anything else continues. `message_var` binds the
|
|
1055
|
+
resolved message first, so a caller that continues or returns can still report
|
|
1056
|
+
why — a message that only reaches the run log is not seen by the operator."""
|
|
1057
|
+
if p.get("message_var"):
|
|
1058
|
+
self.vars[p["message_var"]] = msg
|
|
1059
|
+
if outcome == "stop":
|
|
1060
|
+
raise Stop(msg)
|
|
1061
|
+
if outcome == "return":
|
|
1062
|
+
raise Return(msg)
|
|
1063
|
+
|
|
991
1064
|
def act_verify_context(self, step):
|
|
992
1065
|
"""F2 — the four decl gate forms: required_files (+allow_missing),
|
|
993
1066
|
require_non_empty (+on_empty), require_true (+on_false),
|
|
994
|
-
require_value/equals (+on_mismatch). Messages interpolate {{vars}}.
|
|
1067
|
+
require_value/equals (+on_mismatch). Messages interpolate {{vars}}.
|
|
1068
|
+
Outcomes: stop | return | continue; `message_var` binds a failed gate's message."""
|
|
995
1069
|
p = step.get("parameters") or step
|
|
996
1070
|
if p.get("require_non_empty"):
|
|
997
1071
|
var = p["require_non_empty"]
|
|
@@ -999,8 +1073,7 @@ class Orchestrator:
|
|
|
999
1073
|
if not val:
|
|
1000
1074
|
msg = self.resolve(p.get("on_empty_message", f"{var} is empty"))
|
|
1001
1075
|
self.emit(f"[verify_context] {var} is empty: {msg}")
|
|
1002
|
-
|
|
1003
|
-
raise Stop(msg)
|
|
1076
|
+
self._gate_failed(p, p.get("on_empty"), msg)
|
|
1004
1077
|
else:
|
|
1005
1078
|
self.emit(f"[verify_context] {var} non-empty")
|
|
1006
1079
|
if p.get("require_true"):
|
|
@@ -1011,8 +1084,7 @@ class Orchestrator:
|
|
|
1011
1084
|
if not truthy:
|
|
1012
1085
|
msg = self.resolve(p.get("on_false_message", f"{var} is not satisfied"))
|
|
1013
1086
|
self.emit(f"[verify_context] gate failed ({var}): {msg}")
|
|
1014
|
-
|
|
1015
|
-
raise Stop(msg)
|
|
1087
|
+
self._gate_failed(p, p.get("on_false", "stop"), msg)
|
|
1016
1088
|
else:
|
|
1017
1089
|
self.emit(f"[verify_context] gate ok ({var})")
|
|
1018
1090
|
if p.get("require_value"):
|
|
@@ -1022,14 +1094,15 @@ class Orchestrator:
|
|
|
1022
1094
|
if actual != expected:
|
|
1023
1095
|
msg = self.resolve(p.get("on_mismatch_message", f"{var} != {expected}"))
|
|
1024
1096
|
self.emit(f"[verify_context] value gate failed ({var}={actual!r}): {msg}")
|
|
1025
|
-
|
|
1026
|
-
raise Stop(msg)
|
|
1097
|
+
self._gate_failed(p, p.get("on_mismatch", "stop"), msg)
|
|
1027
1098
|
else:
|
|
1028
1099
|
self.emit(f"[verify_context] value gate ok ({var}=={expected})")
|
|
1029
1100
|
for rf in p.get("required_files", []) or []:
|
|
1030
1101
|
rf = self.resolve(rf)
|
|
1031
1102
|
if rf in self.loaded or os.path.exists(rf):
|
|
1032
1103
|
self.emit(f"[verify_context] present {rf}")
|
|
1104
|
+
elif rf in self.dry_run_unwritten:
|
|
1105
|
+
self.emit(f"[verify_context] present {rf} (dry-run: its author step did not write it)")
|
|
1033
1106
|
elif p.get("allow_missing"):
|
|
1034
1107
|
self.emit(f"[verify_context] missing (allowed) {rf}")
|
|
1035
1108
|
else:
|
|
@@ -1124,7 +1197,7 @@ class Orchestrator:
|
|
|
1124
1197
|
system, sys_files = self._assemble_system(p.get("include_standards", True))
|
|
1125
1198
|
user, ctx_files = self._assemble_user(instr, extra=extra)
|
|
1126
1199
|
self._print_envelope("agent_author", system, sys_files, user, ctx_files, tools, strat)
|
|
1127
|
-
if self.
|
|
1200
|
+
if self._mock_step(strat, system, user, tools, "readonly", step.get("model")):
|
|
1128
1201
|
content = self._mock_author(target, task)
|
|
1129
1202
|
elif strat == "agent":
|
|
1130
1203
|
self._suspend("author", ck, system, user, tools, "readonly",
|
|
@@ -1148,7 +1221,16 @@ class Orchestrator:
|
|
|
1148
1221
|
if not content.strip():
|
|
1149
1222
|
raise Stop(f"step {step.get('id')}: agent_author returned empty content")
|
|
1150
1223
|
if self.opts.dry_run:
|
|
1224
|
+
# The target can be a real project file, so a dry run never writes it.
|
|
1225
|
+
# A later required_files gate counts it as present, and the step's
|
|
1226
|
+
# outputs still bind, so the run reaches the steps after it.
|
|
1151
1227
|
self.emit(f"[agent_author] dry-run: not writing {target}")
|
|
1228
|
+
if target not in self.dry_run_unwritten:
|
|
1229
|
+
self.dry_run_unwritten.append(target)
|
|
1230
|
+
structured = {"status": "ok", "files": [target],
|
|
1231
|
+
"summary": f"dry-run: {target} not written"}
|
|
1232
|
+
self.last_result = structured
|
|
1233
|
+
self._bind(step, structured)
|
|
1152
1234
|
return
|
|
1153
1235
|
self._write_target(target, content)
|
|
1154
1236
|
structured = {"status": "ok", "files": [target], "summary": f"authored {target}"}
|
|
@@ -1205,14 +1287,14 @@ class Orchestrator:
|
|
|
1205
1287
|
system, sys_files = self._assemble_system(p.get("include_standards", True))
|
|
1206
1288
|
user, ctx_files = self._assemble_user(instr, extra=extra)
|
|
1207
1289
|
self._print_envelope("agent_query", system, sys_files, user, ctx_files, tools, strat)
|
|
1208
|
-
if self.
|
|
1290
|
+
if self._mock_step(strat, system, user, tools, "readonly", step.get("model")):
|
|
1209
1291
|
structured = self.resolve_value(p.get("mock")) or {}
|
|
1210
1292
|
elif strat == "agent":
|
|
1211
1293
|
self._suspend("agent_query", ck, system, user, tools, "readonly")
|
|
1212
1294
|
else:
|
|
1213
1295
|
structured = self._structured_fresh(step, strat, system, user, tools)
|
|
1214
1296
|
self.last_result = structured
|
|
1215
|
-
if self.checkpoint:
|
|
1297
|
+
if self.checkpoint and not self.opts.dry_run: # never cache a dry run's mock
|
|
1216
1298
|
self.cache[ck] = structured
|
|
1217
1299
|
self._bind(step, structured)
|
|
1218
1300
|
self.emit(f"[agent_query] strategy={strat or 'mock'} "
|
|
@@ -1381,7 +1463,8 @@ class Orchestrator:
|
|
|
1381
1463
|
"""F9 — run a module .orch.yaml inline: shares vars, envelope, sticky
|
|
1382
1464
|
standards, the caller's scripts map and the replay cache. `with:` seeds
|
|
1383
1465
|
module inputs; cache keys are namespaced under the include id. Stop
|
|
1384
|
-
propagates to the caller (module gates stop the whole skill)
|
|
1466
|
+
propagates to the caller (module gates stop the whole skill); a gate's
|
|
1467
|
+
`return` outcome ends only this module and the caller carries on."""
|
|
1385
1468
|
module_path = self.resolve(step.get("module", ""))
|
|
1386
1469
|
for k, v in (step.get("with", {}) or {}).items():
|
|
1387
1470
|
self.vars[k] = self.resolve_value(v)
|
|
@@ -1398,6 +1481,8 @@ class Orchestrator:
|
|
|
1398
1481
|
self.key_stack.append("inc:" + step.get("id", "include"))
|
|
1399
1482
|
try:
|
|
1400
1483
|
self._run_steps(mod.get("workflow", []) or [])
|
|
1484
|
+
except Return as r:
|
|
1485
|
+
self.emit(f"[include] {module_path} returned early: {str(r)[:200]}")
|
|
1401
1486
|
finally:
|
|
1402
1487
|
self.key_stack.pop()
|
|
1403
1488
|
|
|
@@ -1494,7 +1579,10 @@ class Orchestrator:
|
|
|
1494
1579
|
|
|
1495
1580
|
def run(self):
|
|
1496
1581
|
self._bind_inputs()
|
|
1497
|
-
|
|
1582
|
+
try:
|
|
1583
|
+
self._run_steps(self.wf.get("workflow", []) or [])
|
|
1584
|
+
except Return as r:
|
|
1585
|
+
self.emit(f"[return] workflow ended early: {str(r)[:200]}")
|
|
1498
1586
|
return {"status": "ok", "outputs": self.resolve(self.wf.get("outputs", {}) or {}),
|
|
1499
1587
|
"vars": self.vars, "missing_required": list(self.missing_required),
|
|
1500
1588
|
"reported_not_written": list(self.reported_missing)}
|
|
@@ -1510,6 +1598,7 @@ KNOWN_ACTIONS = {
|
|
|
1510
1598
|
"dispatch_hooks", "user_select", "ask", "loop", "break", "include",
|
|
1511
1599
|
}
|
|
1512
1600
|
STRATEGIES = {"sdk", "sdk-apply", "spawn", "agent"}
|
|
1601
|
+
GATE_OUTCOMES = {"stop", "return", "continue"}
|
|
1513
1602
|
# engine-produced vars a workflow may reference without declaring: the loop/ask
|
|
1514
1603
|
# bindings, plus the context-assembly miss report (_collect_files, item H).
|
|
1515
1604
|
_SPECIAL_VARS = {"selection", "answer", "iteration",
|
|
@@ -1606,6 +1695,8 @@ def validate_workflow(wf, base_dir, path=""):
|
|
|
1606
1695
|
produced.update((s.get("vars") or s.get("parameters") or {}).keys())
|
|
1607
1696
|
elif act == "build_context":
|
|
1608
1697
|
produced.update((s.get("vars") or {}).keys())
|
|
1698
|
+
elif act == "verify_context" and p.get("message_var"):
|
|
1699
|
+
produced.add(p["message_var"])
|
|
1609
1700
|
elif act == "loop":
|
|
1610
1701
|
produced.add(s.get("as", "item"))
|
|
1611
1702
|
produced.add(s.get("as", "item") + "_index")
|
|
@@ -1650,6 +1741,13 @@ def validate_workflow(wf, base_dir, path=""):
|
|
|
1650
1741
|
warnings.append(f"{loc}: ask has no options source (free-text input)")
|
|
1651
1742
|
if action == "user_select" and not p.get("options_from_variable"):
|
|
1652
1743
|
errors.append(f"{loc}: user_select needs options_from_variable")
|
|
1744
|
+
if action == "verify_context":
|
|
1745
|
+
# Any value other than stop/return continues, so a misspelt outcome is a gate
|
|
1746
|
+
# that silently never fires.
|
|
1747
|
+
for k in ("on_empty", "on_false", "on_mismatch"):
|
|
1748
|
+
if p.get(k) is not None and p[k] not in GATE_OUTCOMES:
|
|
1749
|
+
errors.append(f"{loc}: {k} '{p[k]}' is not a gate outcome "
|
|
1750
|
+
f"(known: {', '.join(sorted(GATE_OUTCOMES))})")
|
|
1653
1751
|
if action == "include":
|
|
1654
1752
|
m = step.get("module")
|
|
1655
1753
|
if not m:
|
|
@@ -1766,13 +1864,14 @@ def main(argv=None):
|
|
|
1766
1864
|
"real = spawn the agent profile's CLI per generation")
|
|
1767
1865
|
ap.add_argument("--agent-profile", default="", dest="agent_profile",
|
|
1768
1866
|
help="path to an agent transport profile JSON "
|
|
1769
|
-
"(.claude/.agents/.
|
|
1867
|
+
"(.claude/.agents/.kilo/...); implies --transport real")
|
|
1770
1868
|
ap.add_argument("--isolation", choices=["bare", "fresh"], default="bare",
|
|
1771
1869
|
help="selects the profile's isolation.<mode> args (bare = strongest)")
|
|
1772
1870
|
ap.add_argument("--model", default="", help="model alias/name for the fresh agent")
|
|
1773
1871
|
ap.add_argument("--budget", default="", help="value for the profile's budget_flag, if it declares one")
|
|
1774
1872
|
ap.add_argument("--dry-run", action="store_true", dest="dry_run",
|
|
1775
|
-
help="print the exact command the profile would run
|
|
1873
|
+
help="print the exact command the profile would run for every model "
|
|
1874
|
+
"step, without executing any; each step then takes its mock result")
|
|
1776
1875
|
ap.add_argument("--sdk-install", action="store_true", dest="sdk_install",
|
|
1777
1876
|
help="if the profile's sdk module isn't importable, pip install "
|
|
1778
1877
|
"the package its sdk block declares, then continue")
|
|
@@ -129,11 +129,12 @@ migration is needed (yes/no); any contract change other systems couple to.]
|
|
|
129
129
|
<!-- What this change adds to or alters in the service's PUBLISHED surface — the
|
|
130
130
|
only part of this document the frontend lane may bind against. `None` when
|
|
131
131
|
the surface is unchanged. Omit the section entirely where the code has no
|
|
132
|
-
frontend lane.
|
|
132
|
+
frontend lane. The operation id is the endpoint's name in the published API
|
|
133
|
+
document; an endpoint that already has one keeps it. -->
|
|
133
134
|
|
|
134
|
-
| Contract | Change | Shape | Serves |
|
|
135
|
-
|
|
136
|
-
| `[contract]` | [added / changed] | [request → response field shapes] | IC-[nnn] |
|
|
135
|
+
| Operation id | Contract | Change | Shape | Serves |
|
|
136
|
+
|---|---|---|---|---|
|
|
137
|
+
| `[OperationId]` | `[contract]` | [added / changed] | [request → response field shapes] | IC-[nnn] |
|
|
137
138
|
|
|
138
139
|
## Other open deltas
|
|
139
140
|
|
|
@@ -169,7 +169,9 @@
|
|
|
169
169
|
the titles are the code's answer — the slots below are illustrative
|
|
170
170
|
positions, not a required set. In each: the components that live there,
|
|
171
171
|
their behaviour in signature form, and the contracts they expose or
|
|
172
|
-
require.
|
|
172
|
+
require. A component that PUBLISHES an endpoint names it with its route, verb
|
|
173
|
+
and operation id — the endpoint's name in the published API document, which
|
|
174
|
+
the frontend plan cites and implement looks the endpoint up by. -->
|
|
173
175
|
|
|
174
176
|
### 2.1 [PROJECT_STRUCTURAL_AXIS_NAME]
|
|
175
177
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
rev-templates/domain-constitution.skeleton.md — SHAPE-ONLY reference for the
|
|
3
|
-
domain-constitution SOURCE (guardrails/
|
|
3
|
+
domain-constitution SOURCE (guardrails/[project]/domain/domain-constitution.md),
|
|
4
4
|
the human-readable
|
|
5
5
|
charter the domain half is authored from (so the normal /appweaver
|
|
6
6
|
constitution can re-derive it later). It shows the Project-Identity + Domain-
|
|
@@ -60,4 +60,4 @@ Extends technology [principle ref]. Evidence: [ids]. → [rule file(s)]
|
|
|
60
60
|
|
|
61
61
|
## Domain Requirements Pointer
|
|
62
62
|
|
|
63
|
-
→ guardrails/
|
|
63
|
+
→ guardrails/[project]/domain/domain-requirements.md
|
|
@@ -27,8 +27,8 @@ generated_by: aw-spec-onboard/[VERSION]
|
|
|
27
27
|
=====================================================================
|
|
28
28
|
[DOMAIN: subsection pointers]
|
|
29
29
|
=====================================================================
|
|
30
|
-
[DOMAIN: <subsection>] -> guardrails/
|
|
31
|
-
[DOMAIN: <subsection>] -> guardrails/
|
|
30
|
+
[DOMAIN: <subsection>] -> guardrails/[project]/domain/[generated-file].md
|
|
31
|
+
[DOMAIN: <subsection>] -> guardrails/[project]/domain/[generated-file].md
|
|
32
32
|
[DOMAIN: Platform Topology] -> below
|
|
33
33
|
|
|
34
34
|
=====================================================================
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
rev-templates/domain-rule-bindings.skeleton.md — SHAPE-ONLY reference for the project's
|
|
3
3
|
domain-rule-bindings.md (resolve-domain reads it by that exact name — the symmetric
|
|
4
|
-
twin of tech-rule-bindings.md, which resolve-tech reads). It shows the
|
|
5
|
-
implement
|
|
4
|
+
twin of tech-rule-bindings.md, which resolve-tech reads). It shows the seven headings (plan and
|
|
5
|
+
implement keyed per lane), the RULE PARTITION (neither lane of plan or implement gets a domain file), the bullet shape, and the quick-reference
|
|
6
6
|
table. It lists NO real rule files — every bullet is a placeholder for a file
|
|
7
7
|
the author actually generated from THIS project's evidence.
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ domain principles), and the **gates** (conformance checklist). **plan** and **im
|
|
|
19
19
|
**NO** domain file — the domain reaches them solely through the spec's ids and the context's
|
|
20
20
|
domain-model.md. This is the rule partition; preserve it exactly.
|
|
21
21
|
|
|
22
|
-
- All paths are workspace-relative and live in `guardrails/
|
|
22
|
+
- All paths are workspace-relative and live in `guardrails/[project]/domain/`.
|
|
23
23
|
- `resolve-domain --command <name>` collects the `- ` bullets under `## /<name>`.
|
|
24
24
|
|
|
25
25
|
---
|
|
@@ -27,24 +27,30 @@ domain-model.md. This is the rule partition; preserve it exactly.
|
|
|
27
27
|
## /aw-spec-constitution
|
|
28
28
|
<!-- Domain identity / regulatory / interop sources that become domain principles in
|
|
29
29
|
constitution.domain.md. -->
|
|
30
|
-
- guardrails/
|
|
31
|
-
- guardrails/
|
|
30
|
+
- guardrails/[project]/domain/[identity-or-requirements-file].md
|
|
31
|
+
- guardrails/[project]/domain/[each generated domain-principle source].md
|
|
32
32
|
|
|
33
33
|
## /aw-spec-specify
|
|
34
34
|
<!-- The FULL generated domain set. Specify models the domain and LIFTS the overlay constraints
|
|
35
35
|
into the spec's CN-/DC-/IC- (plan/implement never see these files). -->
|
|
36
|
-
- guardrails/
|
|
36
|
+
- guardrails/[project]/domain/[each generated domain rule file].md
|
|
37
37
|
|
|
38
|
-
## /aw-spec-plan
|
|
39
|
-
_None — plan loads no domain file. Domain reaches
|
|
38
|
+
## /aw-spec-plan-backend
|
|
39
|
+
_None — the service lane of plan loads no domain file. Domain reaches it through the spec and the context's domain-model.md._
|
|
40
40
|
|
|
41
|
-
## /aw-spec-
|
|
42
|
-
_None —
|
|
41
|
+
## /aw-spec-plan-frontend
|
|
42
|
+
_None — the frontend lane of plan loads no domain file. Domain reaches it through the spec and the context's domain-model.md._
|
|
43
|
+
|
|
44
|
+
## /aw-spec-implement-backend
|
|
45
|
+
_None — the service lane of implement loads no domain file. Domain reaches it through backend-plan.md and the context's domain-model.md._
|
|
46
|
+
|
|
47
|
+
## /aw-spec-implement-frontend
|
|
48
|
+
_None — the frontend lane of implement loads no domain file. Domain reaches it through frontend-plan.md and the context's domain-model.md._
|
|
43
49
|
|
|
44
50
|
## /aw-spec-gate
|
|
45
51
|
<!-- Conformance checklist for the gates (analyze-plan · analyze-slice): the domain-derived
|
|
46
52
|
constraints plan/implement were NOT given as authoring input, VALIDATED against output. -->
|
|
47
|
-
- guardrails/
|
|
53
|
+
- guardrails/[project]/domain/[each *.domain.md overlay to validate against].md
|
|
48
54
|
|
|
49
55
|
---
|
|
50
56
|
|
|
@@ -54,6 +60,8 @@ _None — implement loads no domain file. Domain reaches implement through the l
|
|
|
54
60
|
|---|---|---|
|
|
55
61
|
| /aw-spec-constitution | [n] | domain principles |
|
|
56
62
|
| /aw-spec-specify | [n] | author into spec (lift → CN-/DC-/IC-) |
|
|
57
|
-
| /aw-spec-plan | 0 | none — the spec and the domain model carry the domain |
|
|
58
|
-
| /aw-spec-
|
|
63
|
+
| /aw-spec-plan-backend | 0 | none — the spec and the domain model carry the domain |
|
|
64
|
+
| /aw-spec-plan-frontend | 0 | none — the spec and the domain model carry the domain |
|
|
65
|
+
| /aw-spec-implement-backend | 0 | none — backend-plan.md and the domain model carry the domain |
|
|
66
|
+
| /aw-spec-implement-frontend | 0 | none — frontend-plan.md and the domain model carry the domain |
|
|
59
67
|
| /aw-spec-gate | [n] | conformance checklist (validate, don't author) |
|
|
@@ -6,7 +6,7 @@ concern: "[concern-slug]"
|
|
|
6
6
|
<!--
|
|
7
7
|
rev-templates/frontend-rule-file.skeleton.md — SHAPE-ONLY reference for ONE
|
|
8
8
|
generated FRONTEND LANE rule file in the project's frontend/ folder
|
|
9
|
-
(guardrails/
|
|
9
|
+
(guardrails/[project]/frontend/<concern>.md). It is the lane's twin of
|
|
10
10
|
technical-rule-file.skeleton.md, and it exists separately because the lane is a
|
|
11
11
|
different tier: these files are ratified by constitution.ui.md's UX-* series,
|
|
12
12
|
not by the technical half's numerals, and they govern only the code under the
|