create-project-arch 1.1.0 → 1.3.0

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/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +662 -43
  4. package/dist/cli.js +151 -0
  5. package/dist/cli.test.js +191 -0
  6. package/package.json +28 -4
  7. package/templates/arch-ui/.arch/edges/decision_to_domain.json +0 -1
  8. package/templates/arch-ui/.arch/edges/milestone_to_task.json +0 -1
  9. package/templates/arch-ui/.arch/edges/task_to_decision.json +0 -1
  10. package/templates/arch-ui/.arch/edges/task_to_module.json +0 -1
  11. package/templates/arch-ui/.arch/graph.json +0 -1
  12. package/templates/arch-ui/.arch/nodes/decisions.json +0 -1
  13. package/templates/arch-ui/.arch/nodes/domains.json +0 -1
  14. package/templates/arch-ui/.arch/nodes/milestones.json +0 -1
  15. package/templates/arch-ui/.arch/nodes/modules.json +0 -1
  16. package/templates/arch-ui/.arch/nodes/tasks.json +0 -1
  17. package/templates/arch-ui/app/api/health/route.ts +5 -4
  18. package/templates/arch-ui/app/api/node-files/route.ts +6 -1
  19. package/templates/arch-ui/app/api/search/route.ts +0 -1
  20. package/templates/arch-ui/app/work/page.tsx +94 -64
  21. package/templates/arch-ui/components/app-shell.tsx +1 -7
  22. package/templates/arch-ui/components/graph/arch-node.tsx +13 -3
  23. package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +6 -2
  24. package/templates/arch-ui/components/graph/build-initial-graph.ts +215 -221
  25. package/templates/arch-ui/components/graph/graph-types.ts +49 -49
  26. package/templates/arch-ui/components/graph/use-auto-layout.ts +51 -51
  27. package/templates/arch-ui/components/graph/use-connection-validation.ts +48 -48
  28. package/templates/arch-ui/components/graph/use-flow-persistence.ts +38 -38
  29. package/templates/arch-ui/components/graph-canvas.tsx +90 -74
  30. package/templates/arch-ui/components/inspector.tsx +56 -22
  31. package/templates/arch-ui/components/sidebar.tsx +18 -8
  32. package/templates/arch-ui/components/topbar.tsx +8 -11
  33. package/templates/arch-ui/components/work-table.tsx +1 -5
  34. package/templates/arch-ui/components/workspace-context.tsx +2 -1
  35. package/templates/arch-ui/eslint.config.js +2 -2
  36. package/templates/arch-ui/lib/graph-dataset.ts +4 -8
  37. package/templates/arch-ui/lib/graph-schema.ts +1 -4
  38. package/templates/arch-ui/package.json +0 -1
  39. package/templates/arch-ui/tsconfig.json +3 -11
  40. package/templates/architecture-specs/SPEC_TEMPLATE.md +49 -0
  41. package/templates/architecture-specs/example-system.md +42 -0
  42. package/templates/concept-map/concept-map.json +67 -0
  43. package/templates/decisions/DECISION_TEMPLATE.md +53 -0
  44. package/templates/decisions/README.md +19 -0
  45. package/templates/decisions/example-decision.md +45 -0
  46. package/templates/domains/DOMAIN_TEMPLATE.md +43 -0
  47. package/templates/domains/README.md +18 -0
  48. package/templates/domains/api.md +33 -0
  49. package/templates/domains/core.md +33 -0
  50. package/templates/domains/domains.json +19 -0
  51. package/templates/domains/ui.md +34 -0
  52. package/templates/foundation/goals.md +35 -0
  53. package/templates/foundation/project-overview.md +35 -0
  54. package/templates/foundation/prompt.md +23 -0
  55. package/templates/foundation/scope.md +35 -0
  56. package/templates/foundation/user-journey.md +37 -0
  57. package/templates/gap-closure/GAP_CLOSURE_TEMPLATE.md +50 -0
  58. package/templates/gap-closure/README.md +19 -0
  59. package/templates/gap-closure/example-gap-closure.md +43 -0
  60. package/templates/validation-hooks/.githooks/pre-commit +4 -0
  61. package/templates/validation-hooks/README.md +20 -0
  62. package/templates/validation-hooks/scripts/validate.sh +9 -0
@@ -119,7 +119,9 @@ function isTaskLikeType(type: string): type is "task" | "milestone" | "phase" {
119
119
  return type === "task" || type === "milestone" || type === "phase";
120
120
  }
121
121
 
122
- function isFileBackedType(type: string): type is "task" | "milestone" | "phase" | "domain" | "file" {
122
+ function isFileBackedType(
123
+ type: string,
124
+ ): type is "task" | "milestone" | "phase" | "domain" | "file" {
123
125
  return (
124
126
  type === "task" ||
125
127
  type === "milestone" ||
@@ -190,7 +192,11 @@ function humanizeValue(label: string, value: string): string {
190
192
  return value;
191
193
  }
192
194
 
193
- function parseTaskId(taskId: string): { phaseId?: string; milestoneId?: string; taskNumber?: string } {
195
+ function parseTaskId(taskId: string): {
196
+ phaseId?: string;
197
+ milestoneId?: string;
198
+ taskNumber?: string;
199
+ } {
194
200
  const [phaseId, milestoneId, taskNumber] = taskId.split("/");
195
201
  return {
196
202
  phaseId: phaseId || undefined,
@@ -393,23 +399,21 @@ function MetadataDetails({
393
399
  className="rounded-lg border border-slate-700/70 bg-slate-900/30 p-2 [&_summary::-webkit-details-marker]:hidden"
394
400
  open={defaultOpen}
395
401
  >
396
- <summary className="cursor-pointer list-none text-sm font-medium text-slate-200">{title}</summary>
402
+ <summary className="cursor-pointer list-none text-sm font-medium text-slate-200">
403
+ {title}
404
+ </summary>
397
405
  <div className="mt-2 grid gap-2">{children}</div>
398
406
  </details>
399
407
  );
400
408
  }
401
409
 
402
- function MetadataValue({
403
- label,
404
- value,
405
- }: {
406
- label: string;
407
- value: FrontmatterValue;
408
- }) {
410
+ function MetadataValue({ label, value }: { label: string; value: FrontmatterValue }) {
409
411
  if (value === null) {
410
412
  return (
411
413
  <div className="grid gap-1">
412
- <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">{humanizeLabel(label)}</p>
414
+ <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">
415
+ {humanizeLabel(label)}
416
+ </p>
413
417
  <p className="m-0 text-sm text-slate-500">None</p>
414
418
  </div>
415
419
  );
@@ -419,7 +423,9 @@ function MetadataValue({
419
423
  if (value.length === 0) {
420
424
  return (
421
425
  <div className="grid gap-1">
422
- <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">{humanizeLabel(label)}</p>
426
+ <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">
427
+ {humanizeLabel(label)}
428
+ </p>
423
429
  <p className="m-0 text-sm text-slate-500">None</p>
424
430
  </div>
425
431
  );
@@ -428,7 +434,9 @@ function MetadataValue({
428
434
  const listLike = normalizeKey(label).includes("criteria");
429
435
  return (
430
436
  <div className="grid gap-1.5">
431
- <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">{humanizeLabel(label)}</p>
437
+ <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">
438
+ {humanizeLabel(label)}
439
+ </p>
432
440
  {listLike ? (
433
441
  <ol className="m-0 grid list-decimal gap-1 pl-5 text-sm text-slate-200">
434
442
  {value.map((item) => (
@@ -485,7 +493,11 @@ export function InspectorPanel() {
485
493
  keys
486
494
  .filter((key) => sourceEntries.has(key))
487
495
  .map((key) => [key, sourceEntries.get(key) ?? null] as const)
488
- .filter(([, value]) => showEmptyFileMetadata || !(value === null || (Array.isArray(value) && value.length === 0)));
496
+ .filter(
497
+ ([, value]) =>
498
+ showEmptyFileMetadata ||
499
+ !(value === null || (Array.isArray(value) && value.length === 0)),
500
+ );
489
501
 
490
502
  return {
491
503
  identity: pick(keyOrder.identity),
@@ -651,9 +663,15 @@ export function InspectorPanel() {
651
663
  <aside className="h-full min-h-0 overflow-y-auto overflow-x-visible border-l border-slate-800 bg-slate-950/85 p-4">
652
664
  <div className="grid gap-3">
653
665
  <div className="flex flex-wrap items-center gap-2">
654
- <Badge variant="secondary">{humanizeLabel(hasFileDocument ? "file" : selection.type)}</Badge>
655
- {typeof fileLaneValue === "string" ? <Badge variant="secondary">{humanizeValue("lane", fileLaneValue)}</Badge> : null}
656
- {typeof fileStatusValue === "string" ? <Badge variant="secondary">{humanizeValue("status", fileStatusValue)}</Badge> : null}
666
+ <Badge variant="secondary">
667
+ {humanizeLabel(hasFileDocument ? "file" : selection.type)}
668
+ </Badge>
669
+ {typeof fileLaneValue === "string" ? (
670
+ <Badge variant="secondary">{humanizeValue("lane", fileLaneValue)}</Badge>
671
+ ) : null}
672
+ {typeof fileStatusValue === "string" ? (
673
+ <Badge variant="secondary">{humanizeValue("status", fileStatusValue)}</Badge>
674
+ ) : null}
657
675
  {selectedLevelFile?.path ? (
658
676
  <Button
659
677
  variant="outline"
@@ -687,7 +705,9 @@ export function InspectorPanel() {
687
705
  {graphWarnings.length > 0 ? (
688
706
  <Section title="Graph Diagnostics">
689
707
  {graphWarnings.slice(0, 8).map((issue, index) => (
690
- <Code key={`${issue.ruleId}:${index}`}>[{issue.ruleId}] {issue.message}</Code>
708
+ <Code key={`${issue.ruleId}:${index}`}>
709
+ [{issue.ruleId}] {issue.message}
710
+ </Code>
691
711
  ))}
692
712
  </Section>
693
713
  ) : null}
@@ -774,7 +794,11 @@ export function InspectorPanel() {
774
794
  {fileMetadataGroups.references.length > 0 ? (
775
795
  <MetadataDetails title="References">
776
796
  {fileMetadataGroups.references.map(([label, value]) => (
777
- <MetadataValue key={`file-references:${label}`} label={label} value={value} />
797
+ <MetadataValue
798
+ key={`file-references:${label}`}
799
+ label={label}
800
+ value={value}
801
+ />
778
802
  ))}
779
803
  </MetadataDetails>
780
804
  ) : null}
@@ -806,7 +830,11 @@ export function InspectorPanel() {
806
830
  <div className="grid gap-1.5">
807
831
  <p className="text-slate-400">Task Metadata</p>
808
832
  {rawTaskMetadata.map((item) => (
809
- <Field key={`${item.label}:${item.value}`} label={item.label} value={item.value} />
833
+ <Field
834
+ key={`${item.label}:${item.value}`}
835
+ label={item.label}
836
+ value={item.value}
837
+ />
810
838
  ))}
811
839
  </div>
812
840
  ) : null}
@@ -836,7 +864,9 @@ export function InspectorPanel() {
836
864
  {trace?.files?.map((file) => (
837
865
  <Code key={file}>{file}</Code>
838
866
  ))}
839
- {!trace?.decisionRefs?.length && !trace?.moduleRefs?.length && !trace?.files?.length ? (
867
+ {!trace?.decisionRefs?.length &&
868
+ !trace?.moduleRefs?.length &&
869
+ !trace?.files?.length ? (
840
870
  <p className="text-slate-400">No trace links found.</p>
841
871
  ) : null}
842
872
  </div>
@@ -854,7 +884,11 @@ export function InspectorPanel() {
854
884
  <div className="grid gap-2">
855
885
  <p className="text-slate-400">Entity Data</p>
856
886
  {entityDetails.summary.map((item) => (
857
- <Field key={`${item.label}:${item.value}`} label={item.label} value={item.value} />
887
+ <Field
888
+ key={`${item.label}:${item.value}`}
889
+ label={item.label}
890
+ value={item.value}
891
+ />
858
892
  ))}
859
893
  <RelatedList label="Tasks" values={entityDetails.relatedTasks} />
860
894
  <RelatedList label="Milestones" values={entityDetails.relatedMilestones} />
@@ -81,12 +81,17 @@ function sortTree(nodes: TreeNode[]): TreeNode[] {
81
81
  }));
82
82
  }
83
83
 
84
- function buildDocNodes(scope: DomainDocsData["docs"][number]["scope"], docs: DomainDocsData["docs"]): TreeNode[] {
84
+ function buildDocNodes(
85
+ scope: DomainDocsData["docs"][number]["scope"],
86
+ docs: DomainDocsData["docs"],
87
+ ): TreeNode[] {
85
88
  const scopedDocs = docs.filter((doc) => doc.scope === scope);
86
89
  const roots: TreeNode[] = [];
87
90
 
88
91
  for (const doc of scopedDocs) {
89
- const relativePath = doc.path.startsWith(`${scope}/`) ? doc.path.slice(scope.length + 1) : doc.path;
92
+ const relativePath = doc.path.startsWith(`${scope}/`)
93
+ ? doc.path.slice(scope.length + 1)
94
+ : doc.path;
90
95
  const parts = relativePath.split("/").filter(Boolean);
91
96
  if (parts.length === 0) continue;
92
97
 
@@ -162,7 +167,8 @@ export function Sidebar() {
162
167
  }, []);
163
168
 
164
169
  const activeView = useMemo<WorkspaceView>(() => {
165
- if (pathname === "/work" && searchParams.get("view") === "architecture") return "architecture-map";
170
+ if (pathname === "/work" && searchParams.get("view") === "architecture")
171
+ return "architecture-map";
166
172
  if (pathname === "/work" && searchParams.get("view") === "project") return "project-map";
167
173
  if (pathname === "/work") return "tasks-roadmap";
168
174
  const view = searchParams.get("view");
@@ -290,9 +296,7 @@ export function Sidebar() {
290
296
  ) : (
291
297
  <span className="h-4 w-4" />
292
298
  )}
293
- <span className="text-slate-300">
294
- {iconForNode(node, expandedKeys.has(node.key))}
295
- </span>
299
+ <span className="text-slate-300">{iconForNode(node, expandedKeys.has(node.key))}</span>
296
300
  <button
297
301
  type="button"
298
302
  className="min-w-0 flex-1 truncate text-left text-[12px] text-slate-200"
@@ -405,8 +409,14 @@ export function Sidebar() {
405
409
 
406
410
  <NavigationMenu>
407
411
  <section className="mb-4">
408
- <p className="mb-2 text-[11px] uppercase tracking-[0.1em] text-slate-400">Unified Explorer</p>
409
- {tree.length > 0 ? renderTree(tree) : <p className="text-sm text-slate-400">Loading explorer...</p>}
412
+ <p className="mb-2 text-[11px] uppercase tracking-[0.1em] text-slate-400">
413
+ Unified Explorer
414
+ </p>
415
+ {tree.length > 0 ? (
416
+ renderTree(tree)
417
+ ) : (
418
+ <p className="text-sm text-slate-400">Loading explorer...</p>
419
+ )}
410
420
  </section>
411
421
  <section className="mb-4">
412
422
  <p className="mb-2 text-[11px] uppercase tracking-[0.1em] text-slate-400">Node Filters</p>
@@ -32,7 +32,8 @@ export function Topbar({ projectName }: { projectName: string }) {
32
32
  const pathname = usePathname();
33
33
  const searchParams = useSearchParams();
34
34
  const { selection } = useInspector();
35
- const { splitPane, setSplitPane, rightCollapsed, setRightCollapsed, resetLayout } = useWorkspace();
35
+ const { splitPane, setSplitPane, rightCollapsed, setRightCollapsed, resetLayout } =
36
+ useWorkspace();
36
37
 
37
38
  useEffect(() => {
38
39
  function onKeyDown(event: KeyboardEvent) {
@@ -79,11 +80,7 @@ export function Topbar({ projectName }: { projectName: string }) {
79
80
 
80
81
  const breadcrumbs = useMemo(() => {
81
82
  const pathLabel =
82
- pathname === "/work"
83
- ? "Work Graph"
84
- : pathname === "/health"
85
- ? "Health"
86
- : "Workspace";
83
+ pathname === "/work" ? "Work Graph" : pathname === "/health" ? "Health" : "Workspace";
87
84
  const view = searchParams.get("view");
88
85
  const secondary =
89
86
  view === "docs"
@@ -92,11 +89,11 @@ export function Topbar({ projectName }: { projectName: string }) {
92
89
  ? "Decisions"
93
90
  : view === "architecture"
94
91
  ? "Architecture"
95
- : view === "project"
96
- ? "Project"
97
- : view === "tasks"
98
- ? "Tasks"
99
- : "Map";
92
+ : view === "project"
93
+ ? "Project"
94
+ : view === "tasks"
95
+ ? "Tasks"
96
+ : "Map";
100
97
 
101
98
  const parts = [pathLabel];
102
99
  if (pathname === "/work") parts.push(secondary);
@@ -31,11 +31,7 @@ export function WorkTable({ tasks, onSelectTask }: WorkTableProps) {
31
31
  </TableHeader>
32
32
  <TableBody>
33
33
  {tasks.map((task) => (
34
- <TableRow
35
- className="cursor-pointer"
36
- key={task.id}
37
- onClick={() => onSelectTask(task)}
38
- >
34
+ <TableRow className="cursor-pointer" key={task.id} onClick={() => onSelectTask(task)}>
39
35
  <TableCell>
40
36
  <Code>{task.id}</Code>
41
37
  </TableCell>
@@ -84,7 +84,8 @@ function defaultFilters(): WorkspaceFilters {
84
84
 
85
85
  function resolveActiveGraphView(pathname: string, searchParams: URLSearchParams): GraphViewMode {
86
86
  if (pathname === "/work" && searchParams.get("view") === "project") return "project";
87
- if (pathname === "/work" && searchParams.get("view") === "architecture") return "architecture-map";
87
+ if (pathname === "/work" && searchParams.get("view") === "architecture")
88
+ return "architecture-map";
88
89
  if (pathname === "/work") return "tasks";
89
90
  return "architecture-map";
90
91
  }
@@ -1,2 +1,2 @@
1
- import { config } from '@repo/eslint-config/next-js';
2
- export default config;
1
+ import { nextJsConfig } from '@repo/eslint-config/next-js';
2
+ export default nextJsConfig;
@@ -114,14 +114,10 @@ async function filterGitIgnoredPaths(root: string, paths: string[]): Promise<Set
114
114
  if (chunk.length === 0) continue;
115
115
 
116
116
  try {
117
- const { stdout } = (await execFile(
118
- "git",
119
- ["check-ignore", ...chunk],
120
- {
121
- cwd: root,
122
- encoding: "utf8",
123
- } as Parameters<typeof execFile>[2],
124
- )) as { stdout: string };
117
+ const { stdout } = (await execFile("git", ["check-ignore", ...chunk], {
118
+ cwd: root,
119
+ encoding: "utf8",
120
+ } as Parameters<typeof execFile>[2])) as { stdout: string };
125
121
  stdout
126
122
  .split("\n")
127
123
  .map((line: string) => line.trim())
@@ -229,10 +229,7 @@ export const GRAPH_VALIDATION_RULES = [
229
229
  },
230
230
  ] as const;
231
231
 
232
- function pushIssue(
233
- output: GraphValidationResult,
234
- issue: GraphValidationIssue,
235
- ) {
232
+ function pushIssue(output: GraphValidationResult, issue: GraphValidationIssue) {
236
233
  if (issue.severity === "error") output.errors.push(issue);
237
234
  else output.warnings.push(issue);
238
235
  }
@@ -35,4 +35,3 @@
35
35
  "typescript": "5.9.2"
36
36
  }
37
37
  }
38
-
@@ -8,14 +8,6 @@
8
8
  ],
9
9
  "strictNullChecks": true
10
10
  },
11
- "include": [
12
- "**/*.ts",
13
- "**/*.tsx",
14
- "**/*.d.ts",
15
- "next-env.d.ts",
16
- "next.config.js"
17
- ],
18
- "exclude": [
19
- "node_modules"
20
- ]
21
- }
11
+ "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", "next-env.d.ts", "next.config.js"],
12
+ "exclude": ["node_modules"]
13
+ }
@@ -0,0 +1,49 @@
1
+ # System Architecture Specification
2
+
3
+ <!-- Guidance: Use this template for new architecture specs in architecture/architecture/. -->
4
+
5
+ ## Purpose
6
+
7
+ <!-- Guidance: Explain why this system/component exists and the problem it solves. -->
8
+
9
+ ...
10
+
11
+ ## Scope
12
+
13
+ ### In Scope
14
+
15
+ - ...
16
+
17
+ ### Out Of Scope
18
+
19
+ - ...
20
+
21
+ ## Key Definitions
22
+
23
+ <!-- Guidance: Define domain terms and technical vocabulary used in this spec. -->
24
+
25
+ - ...
26
+
27
+ ## Design
28
+
29
+ <!-- Guidance: Describe architecture approach, constraints, and major patterns. -->
30
+
31
+ ...
32
+
33
+ ## Data Model
34
+
35
+ <!-- Guidance: List core entities, relationships, and ownership boundaries. -->
36
+
37
+ - ...
38
+
39
+ ## Owning Domain
40
+
41
+ <!-- Guidance: Specify the domain accountable for this architecture area. -->
42
+
43
+ - ...
44
+
45
+ ## MVP Constraints
46
+
47
+ <!-- Guidance: List minimum viable boundaries and explicit non-goals for initial delivery. -->
48
+
49
+ - ...
@@ -0,0 +1,42 @@
1
+ # Example System Specification
2
+
3
+ <!-- Guidance: Reference implementation of SPEC_TEMPLATE.md for new projects. -->
4
+
5
+ ## Purpose
6
+
7
+ Provide a canonical architecture spec format for teams documenting a system surface.
8
+
9
+ ## Scope
10
+
11
+ ### In Scope
12
+
13
+ - Define required architecture sections and ownership expectations.
14
+ - Establish traceable spec structure for implementation planning.
15
+
16
+ ### Out Of Scope
17
+
18
+ - Detailed API contract definitions.
19
+ - Final production implementation decisions.
20
+
21
+ ## Key Definitions
22
+
23
+ - System surface: a bounded capability area implemented across one or more modules.
24
+ - Ownership boundary: the domain responsible for maintaining this surface.
25
+
26
+ ## Design
27
+
28
+ Use a layered design where interface boundaries, data ownership, and dependency direction are explicit.
29
+
30
+ ## Data Model
31
+
32
+ - `SpecSection`: named architecture section with required content.
33
+ - `OwnershipLink`: maps the system surface to an owning domain.
34
+
35
+ ## Owning Domain
36
+
37
+ - `core`
38
+
39
+ ## MVP Constraints
40
+
41
+ - Keep scope focused on structure and traceability.
42
+ - Defer advanced runtime behavior until downstream milestones.
@@ -0,0 +1,67 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "concepts": [
4
+ {
5
+ "id": "concept-identity-and-access",
6
+ "name": "Identity And Access",
7
+ "description": "Authentication, authorization, and role enforcement boundaries.",
8
+ "owningDomain": "core",
9
+ "moduleResponsibilities": ["packages/api", "packages/types"],
10
+ "implementationSurfaces": [
11
+ {
12
+ "type": "api",
13
+ "path": "packages/api/src/auth"
14
+ },
15
+ {
16
+ "type": "ui",
17
+ "path": "apps/web/app/(auth)"
18
+ }
19
+ ],
20
+ "dependencies": ["concept-user-profile"]
21
+ },
22
+ {
23
+ "id": "concept-user-profile",
24
+ "name": "User Profile",
25
+ "description": "Canonical user metadata and preference lifecycle.",
26
+ "owningDomain": "ui",
27
+ "moduleResponsibilities": ["apps/web", "packages/ui"],
28
+ "implementationSurfaces": [
29
+ {
30
+ "type": "ui",
31
+ "path": "apps/web/app/profile"
32
+ },
33
+ {
34
+ "type": "component",
35
+ "path": "packages/ui/src/profile"
36
+ }
37
+ ],
38
+ "dependencies": []
39
+ }
40
+ ],
41
+ "domainModuleMapping": [
42
+ {
43
+ "domain": "core",
44
+ "module": "packages/api",
45
+ "responsibility": "Policy enforcement and access control services"
46
+ },
47
+ {
48
+ "domain": "ui",
49
+ "module": "apps/web",
50
+ "responsibility": "User-facing workflows and interaction handling"
51
+ }
52
+ ],
53
+ "implementationChecklist": [
54
+ {
55
+ "conceptId": "concept-identity-and-access",
56
+ "checks": [
57
+ "Decision linkage recorded",
58
+ "Primary code targets identified",
59
+ "Verification criteria defined"
60
+ ]
61
+ },
62
+ {
63
+ "conceptId": "concept-user-profile",
64
+ "checks": ["Domain ownership confirmed", "Surface mapping completed", "Dependencies reviewed"]
65
+ }
66
+ ]
67
+ }
@@ -0,0 +1,53 @@
1
+ ---
2
+ id: "project:YYYYMMDD:decision-slug"
3
+ title: "Decision Title"
4
+ slug: "decision-slug"
5
+ status: "proposed"
6
+ createdAt: "YYYY-MM-DD"
7
+ updatedAt: "YYYY-MM-DD"
8
+ relatedTasks: []
9
+ relatedDocs: []
10
+ supersedes: []
11
+ ---
12
+
13
+ ## Decision Title
14
+
15
+ <!-- Guidance: Capture one architecture decision per file. Keep scope explicit and testable. -->
16
+
17
+ ## Context
18
+
19
+ <!-- Guidance: What problem/constraint requires a decision now? -->
20
+
21
+ ...
22
+
23
+ ## Decision
24
+
25
+ <!-- Guidance: State the chosen approach unambiguously. -->
26
+
27
+ ...
28
+
29
+ ## Rationale
30
+
31
+ <!-- Guidance: Why this option was selected versus alternatives. -->
32
+
33
+ ...
34
+
35
+ ## Alternatives Considered
36
+
37
+ - Option A: ...
38
+ - Option B: ...
39
+
40
+ ## Affected Artifacts
41
+
42
+ <!-- Guidance: List code/doc surfaces impacted by this decision. -->
43
+
44
+ - `packages/...`
45
+ - `apps/...`
46
+ - `architecture/...`
47
+
48
+ ## Implementation Status Checklist
49
+
50
+ - [ ] Task links added
51
+ - [ ] Code targets updated
52
+ - [ ] Public docs updated
53
+ - [ ] Validation checks pass
@@ -0,0 +1,19 @@
1
+ # Decision Records
2
+
3
+ <!-- Guidance: Use decision records for meaningful architecture choices and tradeoffs. -->
4
+
5
+ ## Workflow
6
+
7
+ 1. Copy `DECISION_TEMPLATE.md` for each significant decision.
8
+ 2. Set frontmatter (`id`, `title`, `slug`, `status`, timestamps, links).
9
+ 3. Fill Context, Decision, Rationale, Alternatives, and Affected Artifacts.
10
+ 4. Track implementation progress with the checklist.
11
+
12
+ ## Decision Locations
13
+
14
+ - `architecture/decisions/` for architecture-level decision records.
15
+ - `roadmap/decisions/` and milestone decision indexes can reference decision IDs for execution traceability.
16
+
17
+ ## CLI Support
18
+
19
+ Use `pa decision new` to create operational decision records linked into roadmap indexes.
@@ -0,0 +1,45 @@
1
+ ---
2
+ id: "project:20260308:adopt-typed-service-contracts"
3
+ title: "Adopt Typed Service Contracts"
4
+ slug: "adopt-typed-service-contracts"
5
+ status: "accepted"
6
+ createdAt: "2026-03-08"
7
+ updatedAt: "2026-03-08"
8
+ relatedTasks:
9
+ - "phase-1/milestone-1-setup/005"
10
+ relatedDocs:
11
+ - "architecture/architecture/example-system.md"
12
+ supersedes: []
13
+ ---
14
+
15
+ ## Adopt Typed Service Contracts
16
+
17
+ ## Context
18
+
19
+ Service integration points were drifting across modules due to implicit payload assumptions.
20
+
21
+ ## Decision
22
+
23
+ All cross-module service boundaries must use explicit shared types in `packages/types` with runtime validation in API adapters.
24
+
25
+ ## Rationale
26
+
27
+ Typed contracts reduce integration regressions, improve agent traceability, and make architectural intent machine-verifiable.
28
+
29
+ ## Alternatives Considered
30
+
31
+ - Keep implicit JSON contracts and rely on integration tests only.
32
+ - Use per-module ad hoc type aliases without shared contracts.
33
+
34
+ ## Affected Artifacts
35
+
36
+ - `packages/types/src/`
37
+ - `packages/api/src/`
38
+ - `architecture/architecture/example-system.md`
39
+
40
+ ## Implementation Status Checklist
41
+
42
+ - [x] Task links added
43
+ - [x] Code targets updated
44
+ - [x] Public docs updated
45
+ - [x] Validation checks pass
@@ -0,0 +1,43 @@
1
+ # Domain Template
2
+
3
+ <!-- Guidance: Copy this file to `<domain-name>.md` when introducing a new domain. -->
4
+
5
+ ## Domain Name
6
+
7
+ <domain-name>
8
+
9
+ ## Responsibilities
10
+
11
+ <!-- Guidance: What this domain owns and manages. -->
12
+
13
+ - ...
14
+
15
+ ## Primary Data Ownership
16
+
17
+ <!-- Guidance: Entities this domain is the source of truth for. -->
18
+
19
+ - ...
20
+
21
+ ## Interface Contracts
22
+
23
+ <!-- Guidance: APIs/events this domain exposes and consumes. -->
24
+
25
+ - ...
26
+
27
+ ## Non-Goals
28
+
29
+ <!-- Guidance: Explicit boundaries for what this domain does NOT do. -->
30
+
31
+ - ...
32
+
33
+ ## Milestone Mapping
34
+
35
+ <!-- Guidance: Map milestones to domain delivery outcomes. -->
36
+
37
+ - milestone-1: ...
38
+ - milestone-2: ...
39
+
40
+ ## Implementation Surfaces
41
+
42
+ - `apps/...`
43
+ - `packages/...`