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.
- package/assets/.appweaver/ORCH-DIALECT.md +8 -3
- package/assets/.appweaver/STACK-PROFILES.md +87 -8
- package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +30 -0
- package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
- package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +144 -52
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +78 -10
- package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +283 -25
- package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
- package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +202 -40
- package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
- package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +557 -57
- package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
- package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +39 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +28 -7
- package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
- package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +101 -1
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-model.orch.yaml +141 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-ui-prototype.orch.yaml +386 -0
- package/assets/.appweaver/orchestrator/workflows/aw-spec-workflow-model.orch.yaml +135 -0
- package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +772 -216
- package/assets/.appweaver/scripts/bash/aw-xref.sh +4 -1
- package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +708 -209
- package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +4 -1
- package/assets/.appweaver/scripts/python/aw-crstate.py +319 -0
- package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
- package/assets/.appweaver/scripts/python/aw-gaps.py +119 -0
- package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -0
- package/assets/.appweaver/scripts/python/aw-plan-coverage.py +503 -0
- package/assets/.appweaver/scripts/python/aw-plangate.py +216 -0
- package/assets/.appweaver/scripts/python/aw-principles.py +108 -0
- package/assets/.appweaver/scripts/python/aw-stack.py +257 -28
- package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
- package/assets/.appweaver/scripts/python/aw-xref.py +141 -4
- package/assets/.appweaver/stack-profiles/angular.json +26 -0
- package/assets/.appweaver/stack-profiles/blazor-wasm.json +38 -0
- package/assets/.appweaver/stack-profiles/dotnet-webapi.json +39 -0
- package/assets/.appweaver/stack-profiles/java-spring.json +20 -0
- package/assets/.appweaver/stack-profiles/nextjs.json +26 -0
- package/assets/.appweaver/stack-profiles/node-express.json +27 -0
- package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
- package/assets/.appweaver/stack-profiles/react-vite.json +26 -0
- package/assets/.appweaver/stack-profiles/sveltekit.json +26 -0
- package/assets/.appweaver/stack-profiles/vue-vite.json +26 -0
- package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
- package/assets/.appweaver/templates/ui-model-template.md +67 -0
- package/assets/.appweaver/templates/workflow-model-template.md +62 -0
- package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
- package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
- package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
- package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
- package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
- package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
- package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
- package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
- package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +114 -0
- package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
- package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
- package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
- package/package.json +1 -1
- 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)'}"
|
|
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
|
-
|
|
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), "
|
|
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
|
|
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
|