aw-spec-cli 0.1.20 → 0.1.22

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.
Files changed (62) hide show
  1. package/assets/.appweaver/ORCH-DIALECT.md +8 -3
  2. package/assets/.appweaver/STACK-PROFILES.md +87 -8
  3. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
  4. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
  5. package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +144 -52
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
  22. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
  23. package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
  24. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
  25. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
  26. package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
  27. package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
  28. package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
  29. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
  30. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
  31. package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
  32. package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
  33. package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
  34. package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
  35. package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
  36. package/assets/.appweaver/stack-profiles/angular.json +26 -0
  37. package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
  39. package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
  40. package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
  41. package/assets/.appweaver/stack-profiles/node-express.json +27 -0
  42. package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
  43. package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
  44. package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
  45. package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
  46. package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
  47. package/assets/.appweaver/templates/ui-model-template.md +67 -0
  48. package/assets/.appweaver/templates/workflow-model-template.md +62 -0
  49. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
  50. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
  51. package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
  52. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
  53. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
  54. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
  55. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
  56. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
  57. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +114 -0
  58. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
  60. package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
  61. package/package.json +1 -1
  62. package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
@@ -749,6 +749,29 @@ def _referrer_closure(aggregate, bundle_abs):
749
749
  return sorted(seen)
750
750
 
751
751
 
752
+ def _has_code(name, bundle_abs):
753
+ """Does this aggregate have code on disk yet?
754
+
755
+ "Depends on me" and "has code that could break" are different questions, and the
756
+ closure alone answers only the first. Edges come from the PLAN — a feature that has
757
+ been planned and not implemented is a genuine dependant with nothing to re-test, and
758
+ `record` stubs a used sibling's concept when it has none, so its name enters the
759
+ closure either way. `source_files` is the difference: a stub is written with it empty
760
+ and implement's concept-upsert fills it from what it generated.
761
+ """
762
+ path = _resolve_concept(name, bundle_abs)
763
+ if not path:
764
+ return False
765
+ fm, _ = _read_fm(_read(path))
766
+ files = fm.get("source_files")
767
+ if isinstance(files, list):
768
+ return any(str(f).strip() for f in files)
769
+ # The front-matter reader returns a BLOCK list as a list and anything else as the raw
770
+ # scalar, so the `source_files: []` a stub carries arrives as the STRING "[]" — which
771
+ # is empty, and truthy to anyone who does not look inside it.
772
+ return bool(str(files or "").strip(" \t[]"))
773
+
774
+
752
775
  def _concept_title(path):
753
776
  fm, _ = _read_fm(_read(path))
754
777
  t = fm.get("title") if isinstance(fm.get("title"), str) else ""
@@ -995,15 +1018,30 @@ def cmd_list(args):
995
1018
  closure = []
996
1019
  for agg in aggregates:
997
1020
  closure.extend(c for c in _referrer_closure(agg, bundle_abs) if c not in closure)
1021
+ # THE CLOSURE ANSWERS TWO QUESTIONS AND A CALLER USUALLY WANTS ONE. Edges are read off
1022
+ # the PLAN, so a dependant can be fully planned and hold no code at all. A caller that
1023
+ # re-runs TESTS over the blast radius wants `built`: an unbuilt dependant has nothing
1024
+ # to run, and selecting it turns a fence into a failure over an empty test set. A
1025
+ # caller reporting what this change reaches wants the whole closure, with `unbuilt`
1026
+ # named so nobody reads its absence from a test run as "unaffected".
1027
+ built = [c for c in closure if _has_code(c, bundle_abs)]
1028
+ unbuilt = [c for c in closure if c not in built]
998
1029
  print(f"[xref] {', '.join(aggregates)}: {len(users)} downstream user(s) "
999
- f"— {', '.join(sorted({u['using'] for u in users})) or '(none)'}", file=sys.stderr)
1030
+ f"— {', '.join(sorted({u['using'] for u in users})) or '(none)'}"
1031
+ + (f"; {len(unbuilt)} planned but not implemented — {', '.join(unbuilt)}"
1032
+ if unbuilt else ""), file=sys.stderr)
1000
1033
  _emit({"aggregate": aggregate, "aggregates": aggregates,
1001
1034
  "concept": _rel(path, root) if path else "",
1002
1035
  "users": users, "count": len(users), "impact_report": out_rel, "root": root,
1003
1036
  "referrers": direct, "referrers_csv": ",".join(direct),
1004
1037
  "referrer_closure": closure, "referrer_closure_csv": ",".join(closure),
1005
1038
  "referrer_count": len(direct), "referrer_closure_count": len(closure),
1006
- "indirect_count": max(0, len(closure) - len(direct))})
1039
+ "indirect_count": max(0, len(closure) - len(direct)),
1040
+ # The closure split by whether the dependant has code yet.
1041
+ "built_closure": built, "built_closure_csv": ",".join(built),
1042
+ "built_count": len(built),
1043
+ "unbuilt_closure": unbuilt, "unbuilt_closure_csv": ",".join(unbuilt),
1044
+ "unbuilt_count": len(unbuilt)})
1007
1045
  return 0
1008
1046
 
1009
1047
 
@@ -1229,15 +1267,39 @@ def cmd_concept_paths(args):
1229
1267
  missing = [s for s in mapped if s not in existing]
1230
1268
  scope = "feature"
1231
1269
 
1270
+ # NARROWED TO ONE LANE'S SUBTREES. A concept spans every lane that realises the
1271
+ # aggregate — on a fullstack feature that is the service files AND the web files in
1272
+ # one list. A caller generating one lane must not be handed the other's: `implement`
1273
+ # tells each lane to read the code it is about to extend, and a backend pass shown the
1274
+ # UI's files is being invited to touch a subtree it is forbidden to write. Absent, the
1275
+ # whole list stands, which is what every existing caller wants.
1276
+ under = [u.strip().replace(os.sep, "/").lstrip("./") for u in (args.under or []) if u and u.strip()]
1277
+ under = [u if u.endswith("/") else u + "/" for u in under]
1278
+ scoped_out = 0
1279
+ if under:
1280
+ def inside(p):
1281
+ q = p.replace(os.sep, "/").lstrip("./")
1282
+ return any(q.startswith(u) for u in under)
1283
+ before = len(existing)
1284
+ existing = [p for p in existing if inside(p)]
1285
+ declared = [p for p in declared if inside(p)]
1286
+ missing = [p for p in missing if inside(p)]
1287
+ scoped_out = before - len(existing)
1288
+
1232
1289
  note = "" if path else " — no concept recorded yet"
1233
1290
  drift = f", {len(missing)} declared-but-missing" if missing else ""
1234
- print(f"[xref] concept-paths {aggregate}: {len(existing)} file(s) on disk{drift}{note}"
1291
+ narrowed = f", {scoped_out} outside {' '.join(under)}" if scoped_out else ""
1292
+ print(f"[xref] concept-paths {aggregate}: {len(existing)} file(s) on disk{drift}{narrowed}{note}"
1235
1293
  f" (scope: {scope})", file=sys.stderr)
1236
1294
  _emit({"aggregate": aggregate, "concept": _rel(path, root) if path else "",
1237
1295
  "found": bool(path), "scope": scope, "paths": existing, "count": len(existing),
1238
1296
  "csv": ",".join(existing), "declared": declared,
1239
1297
  "declared_count": len(declared), "missing": missing,
1240
- "missing_count": len(missing), "root": root})
1298
+ "missing_count": len(missing), "under": under, "excluded_count": scoped_out,
1299
+ # One block per line, for a prompt that lists what the lane already has.
1300
+ "paths_text": "\n".join(" " + p for p in existing) if existing else
1301
+ " (none — nothing of this aggregate is on disk in this lane yet)",
1302
+ "root": root})
1241
1303
  return 0
1242
1304
 
1243
1305
 
@@ -1418,6 +1480,70 @@ def _resolve_feature(fd, root):
1418
1480
  "shared_with": shared_with, "shared_with_csv": ", ".join(shared_with)}
1419
1481
 
1420
1482
 
1483
+ def _crstate():
1484
+ """aw-crstate, loaded by path — it owns the readers for a change's own documents
1485
+ (`**Aggregates touched**`, the model delta beside the master or in its archive) and
1486
+ duplicating them here is how two answers to one question start to drift."""
1487
+ import importlib.util
1488
+ p = os.path.join(os.path.dirname(os.path.abspath(__file__)), "aw-crstate.py")
1489
+ spec = importlib.util.spec_from_file_location("aw_crstate", p)
1490
+ if not spec or not spec.loader:
1491
+ return None
1492
+ mod = importlib.util.module_from_spec(spec)
1493
+ try:
1494
+ spec.loader.exec_module(mod)
1495
+ except Exception:
1496
+ return None
1497
+ return mod
1498
+
1499
+
1500
+ def cmd_co_owners(args):
1501
+ """THE FEATURES THAT SHARE THIS CHANGE'S AGGREGATES — the half of a merge's blast
1502
+ radius that referrer edges cannot see.
1503
+
1504
+ `list` answers "who DEPENDS on this aggregate": features that reach it through a
1505
+ Cross-Reference edge, transitively. That is a real blast radius and it is not the whole
1506
+ one. A feature that CO-OWNS the aggregate holds no edge to it at all — it is not a user
1507
+ of `Encounter`, it is one of the capabilities Encounter is made of, and its code lives
1508
+ in the very files this merge rewrites. `Encounter.cs` is extended by every feature that
1509
+ claims Encounter, so a change to it can break a co-owner without touching one referrer.
1510
+
1511
+ SCOPED TO WHAT THE CHANGE TOUCHES, not to everything the feature owns. The delta's own
1512
+ `**Aggregates touched**` header is the authority: a feature owning Encounter and
1513
+ CareTeam whose change touches only Encounter must not drag CareTeam's co-owners through
1514
+ a regression fence they cannot have been affected by. Where that header cannot be read
1515
+ the feature's owned aggregates stand in — wider than needed, never narrower, and
1516
+ `source` says which answer this is so a caller can report it honestly.
1517
+ """
1518
+ root = _root(args.root)
1519
+ fd = args.feature_dir
1520
+ fd_abs = fd if os.path.isabs(fd) else os.path.join(root, fd)
1521
+ feature = _feature_from_dir(fd_abs)
1522
+ ctx = args.context_dir or os.path.dirname(os.path.normpath(fd_abs))
1523
+
1524
+ touched, source = [], "delta"
1525
+ cs = _crstate() if args.cr else None
1526
+ if cs:
1527
+ touched = sorted(cs.aggregates(cs.model_delta_of(ctx, args.cr)))
1528
+ if not touched:
1529
+ # No readable model delta: a spec-only change, or a header this cannot parse.
1530
+ touched = _resolve_feature(fd_abs, root)["owned"]
1531
+ source = "feature-owned"
1532
+
1533
+ own = {_key(a) for a in touched}
1534
+ features = sorted({v["feature"] for v in _feature_index(root).values()
1535
+ if _key(v["feature"]) != _key(feature)
1536
+ and own & {_key(a) for a in v["claims"]}}, key=str.lower)
1537
+
1538
+ print(f"[xref] co-owners {feature}: {len(features)} feature(s) share "
1539
+ f"{', '.join(touched) or '(no aggregate)'} (source: {source})", file=sys.stderr)
1540
+ _emit({"feature": feature, "cr_id": args.cr, "source": source,
1541
+ "aggregates": touched, "aggregates_csv": ", ".join(touched),
1542
+ "features": features, "features_csv": ", ".join(features),
1543
+ "count": len(features), "root": root})
1544
+ return 0
1545
+
1546
+
1421
1547
  def cmd_feature_aggregates(args):
1422
1548
  """WHICH AGGREGATE(S) A FEATURE ACTS ON — the name every command that reads the
1423
1549
  bounded context's domain-model.md needs, because the model is organised by
@@ -1826,10 +1952,21 @@ def main(argv):
1826
1952
  cp = sub.add_parser("concept-paths", help="read-only: the slice's source_files (replaces design-paths)")
1827
1953
  cp.add_argument("--aggregate", default="")
1828
1954
  cp.add_argument("--feature-dir", dest="feature_dir", default="")
1955
+ cp.add_argument("--under", action="append", default=[],
1956
+ help="keep only paths inside this subtree; repeatable. Omitted, every "
1957
+ "path the concept records is returned.")
1829
1958
  cp.add_argument("--root", default="")
1830
1959
  cp.add_argument("--bundle", default="knowledge")
1831
1960
  cp.set_defaults(func=cmd_concept_paths)
1832
1961
 
1962
+ cw = sub.add_parser("co-owners", help="read-only: the features that SHARE this change's aggregates (no referrer edge, same files)")
1963
+ cw.add_argument("--cr", default="", help="the change id; its **Aggregates touched** scopes the answer")
1964
+ cw.add_argument("--feature-dir", dest="feature_dir", default="")
1965
+ cw.add_argument("--context-dir", dest="context_dir", default="",
1966
+ help="the bounded context directory; defaults to the feature dir's parent")
1967
+ cw.add_argument("--root", default="")
1968
+ cw.set_defaults(func=cmd_co_owners)
1969
+
1833
1970
  fa = sub.add_parser("feature-aggregates", help="read-only: the aggregate(s) a feature acts on — spec, then the boundary map, then the feature name")
1834
1971
  fa.add_argument("--feature-dir", dest="feature_dir", default="")
1835
1972
  fa.add_argument("--root", default="")
@@ -12,5 +12,31 @@
12
12
  "prototype_run": "npm start --prefix prototype",
13
13
  "detect": [
14
14
  "web/angular.json"
15
+ ],
16
+ "reserved_names": [
17
+ "Array",
18
+ "Date",
19
+ "Document",
20
+ "Element",
21
+ "Error",
22
+ "Event",
23
+ "Headers",
24
+ "History",
25
+ "Image",
26
+ "Location",
27
+ "Map",
28
+ "Node",
29
+ "Notification",
30
+ "Object",
31
+ "Option",
32
+ "Range",
33
+ "Record",
34
+ "Request",
35
+ "Response",
36
+ "Selection",
37
+ "Set",
38
+ "Storage",
39
+ "Text",
40
+ "Worker"
15
41
  ]
16
42
  }
@@ -13,5 +13,43 @@
13
13
  "detect": [
14
14
  "web/**/*.csproj",
15
15
  "web/**/wwwroot/index.html"
16
+ ],
17
+ "reserved_names": [
18
+ "Activity",
19
+ "Array",
20
+ "Attribute",
21
+ "Buffer",
22
+ "Comparer",
23
+ "Convert",
24
+ "Delegate",
25
+ "Dictionary",
26
+ "Directory",
27
+ "Endpoint",
28
+ "Environment",
29
+ "Exception",
30
+ "File",
31
+ "Guid",
32
+ "Index",
33
+ "List",
34
+ "Lock",
35
+ "Math",
36
+ "Object",
37
+ "Path",
38
+ "Progress",
39
+ "Queue",
40
+ "Random",
41
+ "Range",
42
+ "Results",
43
+ "Route",
44
+ "Stack",
45
+ "Stream",
46
+ "String",
47
+ "Task",
48
+ "Thread",
49
+ "Timer",
50
+ "Tuple",
51
+ "Type",
52
+ "Uri",
53
+ "Version"
16
54
  ]
17
55
  }
@@ -5,11 +5,50 @@
5
5
  "language": "C#",
6
6
  "ecosystem": "dotnet",
7
7
  "root": "src/",
8
+ "test_root": "tests/",
8
9
  "build": "dotnet build",
9
10
  "test": "dotnet test",
10
11
  "run": "dotnet run --project {{root}}",
11
12
  "detect": [
12
13
  "*.sln",
13
14
  "src/**/*.csproj"
15
+ ],
16
+ "reserved_names": [
17
+ "Activity",
18
+ "Array",
19
+ "Attribute",
20
+ "Buffer",
21
+ "Comparer",
22
+ "Convert",
23
+ "Delegate",
24
+ "Dictionary",
25
+ "Directory",
26
+ "Endpoint",
27
+ "Environment",
28
+ "Exception",
29
+ "File",
30
+ "Guid",
31
+ "Index",
32
+ "List",
33
+ "Lock",
34
+ "Math",
35
+ "Object",
36
+ "Path",
37
+ "Progress",
38
+ "Queue",
39
+ "Random",
40
+ "Range",
41
+ "Results",
42
+ "Route",
43
+ "Stack",
44
+ "Stream",
45
+ "String",
46
+ "Task",
47
+ "Thread",
48
+ "Timer",
49
+ "Tuple",
50
+ "Type",
51
+ "Uri",
52
+ "Version"
14
53
  ]
15
54
  }
@@ -5,6 +5,7 @@
5
5
  "language": "Java",
6
6
  "ecosystem": "java",
7
7
  "root": "src/main/java/",
8
+ "test_root": "src/test/java/",
8
9
  "run_from": "root",
9
10
  "build": "./mvn clean install",
10
11
  "test": "./mvn test",
@@ -12,5 +13,24 @@
12
13
  "detect": [
13
14
  "pom.xml",
14
15
  "build.gradle"
16
+ ],
17
+ "reserved_names": [
18
+ "Character",
19
+ "Class",
20
+ "Enum",
21
+ "Error",
22
+ "Exception",
23
+ "Integer",
24
+ "Module",
25
+ "Number",
26
+ "Object",
27
+ "Package",
28
+ "Process",
29
+ "Record",
30
+ "Runtime",
31
+ "String",
32
+ "System",
33
+ "Thread",
34
+ "Void"
15
35
  ]
16
36
  }
@@ -12,5 +12,31 @@
12
12
  "prototype_run": "npm run dev --prefix prototype",
13
13
  "detect": [
14
14
  "web/next.config.*"
15
+ ],
16
+ "reserved_names": [
17
+ "Array",
18
+ "Date",
19
+ "Document",
20
+ "Element",
21
+ "Error",
22
+ "Event",
23
+ "Headers",
24
+ "History",
25
+ "Image",
26
+ "Location",
27
+ "Map",
28
+ "Node",
29
+ "Notification",
30
+ "Object",
31
+ "Option",
32
+ "Range",
33
+ "Record",
34
+ "Request",
35
+ "Response",
36
+ "Selection",
37
+ "Set",
38
+ "Storage",
39
+ "Text",
40
+ "Worker"
15
41
  ]
16
42
  }
@@ -5,11 +5,38 @@
5
5
  "language": "Node.js",
6
6
  "ecosystem": "node",
7
7
  "root": "src/",
8
+ "test_root": "tests/",
8
9
  "build": "npm run build",
9
10
  "test": "npm test",
10
11
  "run": "npm run dev",
11
12
  "detect": [
12
13
  "package.json",
13
14
  "src/**/*.ts"
15
+ ],
16
+ "reserved_names": [
17
+ "Array",
18
+ "Date",
19
+ "Document",
20
+ "Element",
21
+ "Error",
22
+ "Event",
23
+ "Headers",
24
+ "History",
25
+ "Image",
26
+ "Location",
27
+ "Map",
28
+ "Node",
29
+ "Notification",
30
+ "Object",
31
+ "Option",
32
+ "Range",
33
+ "Record",
34
+ "Request",
35
+ "Response",
36
+ "Selection",
37
+ "Set",
38
+ "Storage",
39
+ "Text",
40
+ "Worker"
14
41
  ]
15
42
  }
@@ -5,6 +5,7 @@
5
5
  "language": "Python",
6
6
  "ecosystem": "python",
7
7
  "root": "app/",
8
+ "test_root": "tests/",
8
9
  "build": "python -m compileall {{root}}",
9
10
  "test": "pytest",
10
11
  "run": "uvicorn app.main:app --reload",
@@ -12,5 +13,20 @@
12
13
  "pyproject.toml",
13
14
  "requirements.txt",
14
15
  "app/main.py"
16
+ ],
17
+ "reserved_names": [
18
+ "Any",
19
+ "Callable",
20
+ "Dict",
21
+ "Generator",
22
+ "Iterator",
23
+ "List",
24
+ "Mapping",
25
+ "Optional",
26
+ "Sequence",
27
+ "Set",
28
+ "Text",
29
+ "Type",
30
+ "Union"
15
31
  ]
16
32
  }
@@ -13,5 +13,31 @@
13
13
  "detect": [
14
14
  "web/package.json",
15
15
  "web/vite.config.*"
16
+ ],
17
+ "reserved_names": [
18
+ "Array",
19
+ "Date",
20
+ "Document",
21
+ "Element",
22
+ "Error",
23
+ "Event",
24
+ "Headers",
25
+ "History",
26
+ "Image",
27
+ "Location",
28
+ "Map",
29
+ "Node",
30
+ "Notification",
31
+ "Object",
32
+ "Option",
33
+ "Range",
34
+ "Record",
35
+ "Request",
36
+ "Response",
37
+ "Selection",
38
+ "Set",
39
+ "Storage",
40
+ "Text",
41
+ "Worker"
16
42
  ]
17
43
  }
@@ -12,5 +12,31 @@
12
12
  "prototype_run": "npm run dev --prefix prototype",
13
13
  "detect": [
14
14
  "web/svelte.config.*"
15
+ ],
16
+ "reserved_names": [
17
+ "Array",
18
+ "Date",
19
+ "Document",
20
+ "Element",
21
+ "Error",
22
+ "Event",
23
+ "Headers",
24
+ "History",
25
+ "Image",
26
+ "Location",
27
+ "Map",
28
+ "Node",
29
+ "Notification",
30
+ "Object",
31
+ "Option",
32
+ "Range",
33
+ "Record",
34
+ "Request",
35
+ "Response",
36
+ "Selection",
37
+ "Set",
38
+ "Storage",
39
+ "Text",
40
+ "Worker"
15
41
  ]
16
42
  }
@@ -13,5 +13,31 @@
13
13
  "detect": [
14
14
  "web/vite.config.*",
15
15
  "web/src/App.vue"
16
+ ],
17
+ "reserved_names": [
18
+ "Array",
19
+ "Date",
20
+ "Document",
21
+ "Element",
22
+ "Error",
23
+ "Event",
24
+ "Headers",
25
+ "History",
26
+ "Image",
27
+ "Location",
28
+ "Map",
29
+ "Node",
30
+ "Notification",
31
+ "Object",
32
+ "Option",
33
+ "Range",
34
+ "Record",
35
+ "Request",
36
+ "Response",
37
+ "Selection",
38
+ "Set",
39
+ "Storage",
40
+ "Text",
41
+ "Worker"
16
42
  ]
17
43
  }
@@ -7,8 +7,8 @@
7
7
  project reverse-engineered by `onboard` that map points at the project's OWN generated
8
8
  domain rule files and its own charter, both derived from the code — same partition, a
9
9
  different corpus. It carries ONLY domain-specific material:
10
- the domain principles, domain identifiers/terminology, regulatory regime,
11
- interoperability surfaces, and domain governance overrides.
10
+ the project identity, the domain principles, domain identifiers/terminology, regulatory
11
+ regime, interoperability surfaces, and domain governance overrides.
12
12
 
13
13
  ══ THIS FILE IS SHAPE. THE MAPPED CORPUS IS CONTENT. ═════════════════════════════
14
14
  This template is part of the kit and governs projects in any industry. It therefore
@@ -35,6 +35,21 @@
35
35
  the detail. That keeps the authority/detail overlap to one line.
36
36
  -->
37
37
 
38
+ ## Project Identity
39
+
40
+ [PROJECT_IDENTITY_CONTENT]
41
+ <!--
42
+ Form: what this project IS in its domain — the regulated role it plays, the bounded
43
+ contexts it is organised into, and who consumes it and through what surface — each as a
44
+ short labelled line. Take them from the mapped charter's own identity section. Where the
45
+ project's domain-requirements states a different value (a different context list, a
46
+ narrower role), the project value is a DEVIATION: ratify it here, naming the charter line
47
+ it departs from and who approved it.
48
+
49
+ This section exists because specify loads THIS file and not the charter, so it is the
50
+ only way the project's contexts and consumers reach specify.
51
+ -->
52
+
38
53
  ## Adopted Modelling Standard
39
54
 
40
55
  [MODELLING_STANDARD_STATEMENT]
@@ -0,0 +1,67 @@
1
+ # UI Model: [Feature Name]
2
+
3
+ **Aggregate (feature)**: `[Aggregate]`
4
+ **Spec Reference**: `specs/[Aggregate]/spec.md`
5
+ **Workflow Reference**: `specs/[Aggregate]/ui/workflow-model.md`
6
+ **Version**: v1.0
7
+ **Status**: Active
8
+
9
+ ## Overview
10
+
11
+ [Information architecture mapping user workflows into concrete UI surfaces, routes, interaction states, and component hierarchy.]
12
+
13
+ ## UI Surfaces
14
+
15
+ ### Surface 1: [Surface Title — e.g. Project Directory & Workspace]
16
+
17
+ - **Surface Type**: `Page` | `Sub-Page` | `Dialog` | `Drawer` | `Modal` | `View`
18
+ - **Route / Mount Path**: `/projects`
19
+ - **Supported Workflows**: [WF-1, WF-2]
20
+ - **Target Personas**: [Project Manager, PMO Member]
21
+ - **Required Interaction States**:
22
+ - `Loading`: Skeleton placeholders during data retrieval
23
+ - `Empty`: Zero-state graphic with call-to-action
24
+ - `Populated`: Structured table / card layout displaying primary entities
25
+ - `Filtered / Searched`: Dynamic filtering view
26
+ - `Error`: Inline error notification banner
27
+ - **User Actions & Events**:
28
+ - Search & filter inputs
29
+ - Action trigger (e.g. "Create Project" → opens Surface 2)
30
+ - Row item selection (→ opens Surface 3)
31
+ - **Transitions**:
32
+ - `On Click [Action]` → Navigates to or opens `[Surface Name]`
33
+
34
+ ---
35
+
36
+ ### Surface 2: [Surface Title — e.g. Create Project Form]
37
+
38
+ - **Surface Type**: `Drawer` | `Dialog` | `Page`
39
+ - **Route / Mount Path**: `/projects/create` (or Modal on `/projects`)
40
+ - **Supported Workflows**: [WF-1]
41
+ - **Target Personas**: [Project Manager]
42
+ - **Required Interaction States**:
43
+ - `Initial / Ready`: Blank form with active validation rules
44
+ - `Validation Error`: Inline field error callouts (e.g. invalid date range)
45
+ - `Submitting`: Button loading spinner with disabled inputs
46
+ - `Success`: Dismisses modal & appends item to active list with toast confirmation
47
+ - **User Actions & Events**:
48
+ - Input fields & attachment uploads
49
+ - Submit & Cancel controls
50
+ - **Transitions**:
51
+ - `On Submit Success` → Return to `Surface 1` (Populated state)
52
+ - `On Cancel` → Close surface without saving
53
+
54
+ ---
55
+
56
+ ### Surface 3: [Surface Title — e.g. Action Confirmation Modal]
57
+
58
+ - **Surface Type**: `Dialog`
59
+ - **Supported Workflows**: [WF-3]
60
+ - **Target Personas**: [Authorized Roles]
61
+ - **Required Interaction States**:
62
+ - `Prompt`: Mandatory confirmation text & action buttons
63
+ - `Executing`: Disables actions during execution
64
+ - **User Actions & Events**:
65
+ - Confirm & Cancel buttons
66
+ - **Transitions**:
67
+ - `On Confirm` → Execute state transition & update parent surface UI