@arcbridge/core 0.2.1 → 0.3.1

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/dist/index.js CHANGED
@@ -1,103 +1,92 @@
1
1
  // src/schemas/config.ts
2
+ import { z as z2 } from "zod";
3
+
4
+ // src/schemas/quality-scenarios.ts
2
5
  import { z } from "zod";
3
- var ServiceSchema = z.object({
6
+ var QualityCategorySchema = z.string().regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/, "Must be lowercase kebab-case (e.g., 'security', 'data-integrity')");
7
+ var QUALITY_PRIORITIES_DESCRIPTION = "Quality priorities in order. Common: security, performance, accessibility, reliability, maintainability, usability, portability, compatibility. Custom categories like data-integrity or compliance are also supported.";
8
+ var QualityPrioritySchema = z.enum(["must", "should", "could"]);
9
+ var QualityScenarioStatusSchema = z.enum([
10
+ "passing",
11
+ "failing",
12
+ "untested",
13
+ "partial"
14
+ ]);
15
+ var QualityScenarioSchema = z.object({
16
+ id: z.string().regex(/^[A-Z0-9]+-\d+$/, "Must match pattern like SEC-01"),
4
17
  name: z.string().min(1),
5
- path: z.string().default("."),
6
- type: z.enum(["nextjs", "react", "fastify", "express", "hono", "dotnet", "unity"]),
7
- tsconfig: z.string().optional(),
8
- csproj: z.string().optional()
18
+ category: QualityCategorySchema,
19
+ priority: QualityPrioritySchema,
20
+ scenario: z.string().min(1),
21
+ expected: z.string().min(1),
22
+ linked_code: z.array(z.string()).default([]),
23
+ linked_tests: z.array(z.string()).default([]),
24
+ linked_blocks: z.array(z.string()).default([]),
25
+ verification: z.enum(["automatic", "manual", "semi-automatic"]),
26
+ status: QualityScenarioStatusSchema.default("untested")
9
27
  });
10
- var ArcBridgeConfigSchema = z.object({
28
+ var QualityGoalSchema = z.object({
29
+ id: QualityCategorySchema,
30
+ priority: z.number().int().min(1),
31
+ description: z.string().min(1)
32
+ });
33
+ var QualityScenariosFileSchema = z.object({
11
34
  schema_version: z.literal(1).default(1),
12
- project_name: z.string().min(1),
13
- project_type: z.enum([
35
+ last_updated: z.string(),
36
+ quality_goals: z.array(QualityGoalSchema),
37
+ scenarios: z.array(QualityScenarioSchema)
38
+ });
39
+
40
+ // src/schemas/config.ts
41
+ var ServiceSchema = z2.object({
42
+ name: z2.string().min(1),
43
+ path: z2.string().default("."),
44
+ type: z2.enum(["nextjs", "react", "fastify", "express", "hono", "dotnet", "unity"]),
45
+ tsconfig: z2.string().optional(),
46
+ csproj: z2.string().optional()
47
+ });
48
+ var ArcBridgeConfigSchema = z2.object({
49
+ schema_version: z2.literal(1).default(1),
50
+ project_name: z2.string().min(1),
51
+ project_type: z2.enum([
14
52
  "nextjs-app-router",
15
53
  "react-vite",
16
54
  "api-service",
17
55
  "dotnet-webapi",
18
56
  "unity-game"
19
57
  ]).default("nextjs-app-router"),
20
- services: z.array(ServiceSchema).default([]),
21
- platforms: z.array(z.enum(["claude", "copilot", "gemini", "codex"])).default(["claude"]),
22
- quality_priorities: z.array(
23
- z.enum([
24
- "security",
25
- "performance",
26
- "accessibility",
27
- "reliability",
28
- "maintainability"
29
- ])
30
- ).default(["security", "performance", "accessibility"]),
31
- indexing: z.object({
32
- include: z.array(z.string()).default(["src/**/*", "app/**/*"]),
33
- exclude: z.array(z.string()).default(["node_modules", "dist", ".next", "coverage"]),
34
- default_mode: z.enum(["fast", "deep"]).default("fast"),
35
- csharp_indexer: z.enum(["auto", "roslyn", "tree-sitter"]).default("auto").describe(
58
+ services: z2.array(ServiceSchema).default([]),
59
+ platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex"])).default(["claude"]),
60
+ quality_priorities: z2.array(QualityCategorySchema).default(["security", "performance", "accessibility"]).describe(QUALITY_PRIORITIES_DESCRIPTION),
61
+ indexing: z2.object({
62
+ include: z2.array(z2.string()).default(["src/**/*", "app/**/*"]),
63
+ exclude: z2.array(z2.string()).default(["node_modules", "dist", ".next", "coverage"]),
64
+ default_mode: z2.enum(["fast", "deep"]).default("fast"),
65
+ csharp_indexer: z2.enum(["auto", "roslyn", "tree-sitter"]).default("auto").describe(
36
66
  "C# indexer backend: 'auto' prefers the arcbridge-dotnet-indexer global tool, falls back to monorepo source if dotnet CLI is available, else tree-sitter. 'tree-sitter' works without .NET SDK. 'roslyn' requires global tool or monorepo source + .NET SDK."
37
67
  )
38
68
  }).default({}),
39
- testing: z.object({
40
- test_command: z.string().min(1).default("npx vitest run").describe("Command to run tests (space-separated, no shell syntax). File paths are appended as arguments."),
41
- timeout_ms: z.number().int().min(1e3).default(6e4).describe("Timeout per test run in milliseconds")
69
+ testing: z2.object({
70
+ test_command: z2.string().min(1).default("npx vitest run").describe("Command to run tests (space-separated, no shell syntax). File paths are appended as arguments."),
71
+ timeout_ms: z2.number().int().min(1e3).default(6e4).describe("Timeout per test run in milliseconds")
42
72
  }).default({}),
43
- drift: z.object({
44
- ignore_paths: z.array(z.string()).default([]).describe(
73
+ drift: z2.object({
74
+ ignore_paths: z2.array(z2.string()).default([]).describe(
45
75
  "File paths or prefixes to ignore in undocumented_module drift checks. Framework files (e.g. next.config.ts, root layout/page) are auto-ignored for known project types."
46
76
  )
47
77
  }).default({}),
48
- metrics: z.object({
49
- auto_record: z.boolean().default(false).describe(
78
+ metrics: z2.object({
79
+ auto_record: z2.boolean().default(false).describe(
50
80
  "Automatically record agent activity (tool name, duration, quality snapshot) when key MCP tools are invoked. Token/model info is optional and caller-provided."
51
81
  )
52
82
  }).default({}),
53
- sync: z.object({
54
- auto_detect_drift: z.boolean().default(true),
55
- drift_severity_threshold: z.enum(["info", "warning", "error"]).default("warning"),
56
- propose_updates_on: z.enum(["session-end", "phase-complete", "manual"]).default("phase-complete")
83
+ sync: z2.object({
84
+ auto_detect_drift: z2.boolean().default(true),
85
+ drift_severity_threshold: z2.enum(["info", "warning", "error"]).default("warning"),
86
+ propose_updates_on: z2.enum(["session-end", "phase-complete", "manual"]).default("phase-complete")
57
87
  }).default({})
58
88
  });
59
89
 
60
- // src/schemas/quality-scenarios.ts
61
- import { z as z2 } from "zod";
62
- var QualityCategorySchema = z2.enum([
63
- "security",
64
- "performance",
65
- "accessibility",
66
- "reliability",
67
- "maintainability"
68
- ]);
69
- var QualityPrioritySchema = z2.enum(["must", "should", "could"]);
70
- var QualityScenarioStatusSchema = z2.enum([
71
- "passing",
72
- "failing",
73
- "untested",
74
- "partial"
75
- ]);
76
- var QualityScenarioSchema = z2.object({
77
- id: z2.string().regex(/^[A-Z0-9]+-\d+$/, "Must match pattern like SEC-01"),
78
- name: z2.string().min(1),
79
- category: QualityCategorySchema,
80
- priority: QualityPrioritySchema,
81
- scenario: z2.string().min(1),
82
- expected: z2.string().min(1),
83
- linked_code: z2.array(z2.string()).default([]),
84
- linked_tests: z2.array(z2.string()).default([]),
85
- linked_blocks: z2.array(z2.string()).default([]),
86
- verification: z2.enum(["automatic", "manual", "semi-automatic"]),
87
- status: QualityScenarioStatusSchema.default("untested")
88
- });
89
- var QualityGoalSchema = z2.object({
90
- id: QualityCategorySchema,
91
- priority: z2.number().int().min(1),
92
- description: z2.string().min(1)
93
- });
94
- var QualityScenariosFileSchema = z2.object({
95
- schema_version: z2.literal(1).default(1),
96
- last_updated: z2.string(),
97
- quality_goals: z2.array(QualityGoalSchema),
98
- scenarios: z2.array(QualityScenarioSchema)
99
- });
100
-
101
90
  // src/schemas/building-blocks.ts
102
91
  import { z as z3 } from "zod";
103
92
  var BuildingBlockSchema = z3.object({
@@ -832,6 +821,41 @@ ${input.quality_priorities.map((q, i) => `| ${i + 1} | ${q} | *Describe what ${q
832
821
  };
833
822
  }
834
823
 
824
+ // src/templates/arc42/02-constraints.ts
825
+ function constraintsTemplate(input) {
826
+ return {
827
+ frontmatter: {
828
+ section: "constraints",
829
+ schema_version: 1
830
+ },
831
+ body: `# Architecture Constraints
832
+
833
+ Document the constraints that shape architectural decisions for ${input.name}. Constraints are non-negotiable requirements from the environment \u2014 they are not chosen, they are given.
834
+
835
+ ## Technical Constraints
836
+
837
+ | Constraint | Description |
838
+ |-----------|-------------|
839
+ | *e.g., Must run on Node.js 22+* | *Explain why this constraint exists* |
840
+ | *e.g., No native dependencies* | *Required for CI/CD compatibility* |
841
+
842
+ ## Organizational Constraints
843
+
844
+ | Constraint | Description |
845
+ |-----------|-------------|
846
+ | *e.g., Team of 1-2 developers* | *Impacts architecture complexity budget* |
847
+ | *e.g., Must ship MVP in 4 weeks* | *Impacts scope and technology choices* |
848
+
849
+ ## Legal / Compliance Constraints
850
+
851
+ | Constraint | Description |
852
+ |-----------|-------------|
853
+ | *e.g., GDPR compliance required* | *Impacts data storage and processing* |
854
+ | *e.g., MIT-compatible dependencies only* | *Impacts library selection* |
855
+ `
856
+ };
857
+ }
858
+
835
859
  // src/templates/arc42/03-context.ts
836
860
  function techStack(template) {
837
861
  switch (template) {
@@ -887,6 +911,47 @@ ${techStack(input.template)}
887
911
  };
888
912
  }
889
913
 
914
+ // src/templates/arc42/04-solution-strategy.ts
915
+ function solutionStrategyTemplate(input) {
916
+ return {
917
+ frontmatter: {
918
+ section: "solution-strategy",
919
+ schema_version: 1
920
+ },
921
+ body: `# Solution Strategy
922
+
923
+ Document the fundamental decisions and solution approaches that drive the architecture of ${input.name}. This section explains *why* the architecture looks the way it does \u2014 connecting quality goals to technical decisions.
924
+
925
+ ## Technology Decisions
926
+
927
+ | Decision | Rationale | Quality Goal |
928
+ |----------|-----------|-------------|
929
+ | *e.g., Use TypeScript* | *Type safety reduces runtime errors* | *Reliability, Maintainability* |
930
+ | *e.g., Use SQLite for local storage* | *Zero-config, no external service dependency* | *Maintainability* |
931
+
932
+ ## Architecture Approach
933
+
934
+ *Describe the high-level architecture pattern and why it was chosen.*
935
+
936
+ - *e.g., Layered architecture with clear module boundaries (enforced by ArcBridge building blocks)*
937
+ - *e.g., YAML as source of truth, DB as queryable cache \u2014 enables version control and human readability*
938
+
939
+ ## Quality Goal Strategies
940
+
941
+ | Quality Goal | Strategy |
942
+ |-------------|----------|
943
+ ${input.quality_priorities.map((q) => `| ${q} | *How will you achieve ${q}?* |`).join("\n")}
944
+
945
+ ## Decomposition Approach
946
+
947
+ *How is the system broken down into building blocks? What principles guide the decomposition?*
948
+
949
+ - *e.g., Feature-based: each major feature is a building block with clear interfaces*
950
+ - *e.g., Layer-based: presentation, business logic, data access*
951
+ `
952
+ };
953
+ }
954
+
890
955
  // src/templates/arc42/05-building-blocks.ts
891
956
  import { existsSync as existsSync2, readdirSync } from "fs";
892
957
  import { join as join3 } from "path";
@@ -2297,7 +2362,9 @@ function generateArc42(targetDir, input) {
2297
2362
  mkdirSync2(decisionsDir, { recursive: true });
2298
2363
  const sections = [
2299
2364
  { file: "01-introduction.md", template: introductionTemplate },
2365
+ { file: "02-constraints.md", template: constraintsTemplate },
2300
2366
  { file: "03-context.md", template: contextTemplate },
2367
+ { file: "04-solution-strategy.md", template: solutionStrategyTemplate },
2301
2368
  { file: "05-building-blocks.md", template: buildingBlocksTemplate },
2302
2369
  { file: "06-runtime-views.md", template: runtimeViewsTemplate },
2303
2370
  { file: "07-deployment.md", template: deploymentTemplate },
@@ -2335,7 +2402,7 @@ function phasePlanTemplate(_input) {
2335
2402
  id: "phase-0-setup",
2336
2403
  name: "Project Setup",
2337
2404
  phase_number: 0,
2338
- status: "in-progress",
2405
+ status: "planned",
2339
2406
  description: "Initialize project structure, install dependencies, configure tooling",
2340
2407
  gate_requirements: [
2341
2408
  "Project builds successfully",
@@ -2600,7 +2667,7 @@ function phasePlanTemplate2(_input) {
2600
2667
  id: "phase-0-setup",
2601
2668
  name: "Project Setup",
2602
2669
  phase_number: 0,
2603
- status: "in-progress",
2670
+ status: "planned",
2604
2671
  description: "Initialize Vite + React project, configure TypeScript, install dependencies",
2605
2672
  gate_requirements: [
2606
2673
  "Project builds successfully with Vite",
@@ -2866,7 +2933,7 @@ function phasePlanTemplate3(_input) {
2866
2933
  id: "phase-0-setup",
2867
2934
  name: "Project Setup",
2868
2935
  phase_number: 0,
2869
- status: "in-progress",
2936
+ status: "planned",
2870
2937
  description: "Initialize API service, configure TypeScript, set up database and middleware",
2871
2938
  gate_requirements: [
2872
2939
  "Project builds successfully",
@@ -3049,7 +3116,7 @@ function phasePlanTemplate4(_input) {
3049
3116
  id: "phase-0-setup",
3050
3117
  name: "Project Setup",
3051
3118
  phase_number: 0,
3052
- status: "in-progress",
3119
+ status: "planned",
3053
3120
  description: "Initialize ASP.NET Core project, configure DI, set up database and middleware pipeline",
3054
3121
  gate_requirements: [
3055
3122
  "Project builds and runs successfully",
@@ -3248,7 +3315,7 @@ function phasePlanTemplate5(_input) {
3248
3315
  id: "phase-0-setup",
3249
3316
  name: "Project Setup",
3250
3317
  phase_number: 0,
3251
- status: "in-progress",
3318
+ status: "planned",
3252
3319
  description: "Initialize Unity project structure, configure input system, core game loop, and assembly definitions",
3253
3320
  gate_requirements: [
3254
3321
  "Unity project opens and runs without errors",
@@ -3533,7 +3600,9 @@ You are responsible for maintaining these sections in \`.arcbridge/arc42/\`. Upd
3533
3600
  | Section | File | When to update |
3534
3601
  |---------|------|----------------|
3535
3602
  | **01 Introduction & Goals** | \`01-introduction.md\` | When project scope, stakeholders, or key goals change |
3603
+ | **02 Architecture Constraints** | \`02-constraints.md\` | When new constraints are discovered (technical, organizational, legal) |
3536
3604
  | **03 Context & Scope** | \`03-context.md\` | When adding/removing external systems, APIs, or integrations |
3605
+ | **04 Solution Strategy** | \`04-solution-strategy.md\` | When fundamental technology or architecture decisions change |
3537
3606
  | **05 Building Blocks** | \`05-building-blocks.md\` | When adding new modules, changing responsibilities, or restructuring layers |
3538
3607
  | **06 Runtime Views** | \`06-runtime-views.md\` | When adding key workflows (e.g., auth flow, order processing, data sync) |
3539
3608
  | **07 Deployment** | \`07-deployment.md\` | When changing infrastructure, environments, or deployment strategy |
@@ -7200,20 +7269,26 @@ function safeParseJson(value, fallback) {
7200
7269
  import { join as join15 } from "path";
7201
7270
  import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync } from "fs";
7202
7271
  import { parse as parse2, stringify as stringify4 } from "yaml";
7272
+ function readTaskFile(projectRoot, phaseId) {
7273
+ const path = join15(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
7274
+ if (!existsSync8(path)) return { error: "not-found" };
7275
+ const raw = readFileSync8(path, "utf-8");
7276
+ const result = TaskFileSchema.safeParse(parse2(raw));
7277
+ if (!result.success) return { error: "invalid" };
7278
+ return { data: result.data, path };
7279
+ }
7280
+ function readPhasesFile(projectRoot) {
7281
+ const path = join15(projectRoot, ".arcbridge", "plan", "phases.yaml");
7282
+ if (!existsSync8(path)) return { error: "not-found" };
7283
+ const raw = readFileSync8(path, "utf-8");
7284
+ const result = PhasesFileSchema.safeParse(parse2(raw));
7285
+ if (!result.success) return { error: "invalid" };
7286
+ return { data: result.data, path };
7287
+ }
7203
7288
  function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
7204
- const taskPath = join15(
7205
- projectRoot,
7206
- ".arcbridge",
7207
- "plan",
7208
- "tasks",
7209
- `${phaseId}.yaml`
7210
- );
7211
- if (!existsSync8(taskPath)) return;
7212
- const raw = readFileSync8(taskPath, "utf-8");
7213
- const parsed = parse2(raw);
7214
- const result = TaskFileSchema.safeParse(parsed);
7215
- if (!result.success) return;
7216
- const taskFile = result.data;
7289
+ const readResult = readTaskFile(projectRoot, phaseId);
7290
+ if ("error" in readResult) return;
7291
+ const { data: taskFile, path: taskPath } = readResult;
7217
7292
  const task = taskFile.tasks.find((t) => t.id === taskId);
7218
7293
  if (!task) return;
7219
7294
  task.status = status;
@@ -7226,39 +7301,19 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
7226
7301
  }
7227
7302
  function addTaskToYaml(projectRoot, phaseId, task) {
7228
7303
  const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
7229
- const taskPath = join15(tasksDir, `${phaseId}.yaml`);
7230
7304
  mkdirSync5(tasksDir, { recursive: true });
7231
- let taskFile;
7232
- if (existsSync8(taskPath)) {
7233
- const raw = readFileSync8(taskPath, "utf-8");
7234
- const parsed = parse2(raw);
7235
- const result = TaskFileSchema.safeParse(parsed);
7236
- if (result.success) {
7237
- taskFile = result.data;
7238
- } else {
7239
- taskFile = { schema_version: 1, phase_id: phaseId, tasks: [] };
7240
- }
7241
- } else {
7242
- taskFile = { schema_version: 1, phase_id: phaseId, tasks: [] };
7243
- }
7305
+ const readResult = readTaskFile(projectRoot, phaseId);
7306
+ const taskFile = "error" in readResult ? { schema_version: 1, phase_id: phaseId, tasks: [] } : readResult.data;
7244
7307
  if (!taskFile.tasks.some((t) => t.id === task.id)) {
7245
7308
  taskFile.tasks.push(task);
7246
7309
  }
7310
+ const taskPath = join15(tasksDir, `${phaseId}.yaml`);
7247
7311
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
7248
7312
  }
7249
7313
  function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
7250
- const phasesPath = join15(
7251
- projectRoot,
7252
- ".arcbridge",
7253
- "plan",
7254
- "phases.yaml"
7255
- );
7256
- if (!existsSync8(phasesPath)) return;
7257
- const raw = readFileSync8(phasesPath, "utf-8");
7258
- const parsed = parse2(raw);
7259
- const result = PhasesFileSchema.safeParse(parsed);
7260
- if (!result.success) return;
7261
- const phasesFile = result.data;
7314
+ const readResult = readPhasesFile(projectRoot);
7315
+ if ("error" in readResult) return;
7316
+ const { data: phasesFile, path: phasesPath } = readResult;
7262
7317
  const phase = phasesFile.phases.find((p) => p.id === phaseId);
7263
7318
  if (!phase) return;
7264
7319
  phase.status = status;
@@ -7268,21 +7323,14 @@ function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
7268
7323
  }
7269
7324
  function addPhaseToYaml(projectRoot, phase) {
7270
7325
  try {
7271
- const phasesPath = join15(
7272
- projectRoot,
7273
- ".arcbridge",
7274
- "plan",
7275
- "phases.yaml"
7276
- );
7277
- if (!existsSync8(phasesPath)) {
7278
- return { success: false, warning: "phases.yaml not found" };
7279
- }
7280
- const raw = readFileSync8(phasesPath, "utf-8");
7281
- const result = PhasesFileSchema.safeParse(parse2(raw));
7282
- if (!result.success) {
7283
- return { success: false, warning: "phases.yaml failed validation" };
7326
+ const readResult = readPhasesFile(projectRoot);
7327
+ if ("error" in readResult) {
7328
+ return {
7329
+ success: false,
7330
+ warning: readResult.error === "not-found" ? "phases.yaml not found" : "phases.yaml failed validation"
7331
+ };
7284
7332
  }
7285
- const phasesFile = result.data;
7333
+ const { data: phasesFile, path: phasesPath } = readResult;
7286
7334
  const existingById = phasesFile.phases.some((p) => p.id === phase.id);
7287
7335
  const existingByNumber = phasesFile.phases.some((p) => p.phase_number === phase.phase_number);
7288
7336
  if (existingByNumber && !existingById) {
@@ -7349,25 +7397,17 @@ function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verifi
7349
7397
  }
7350
7398
  function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
7351
7399
  try {
7352
- const taskPath = join15(
7353
- projectRoot,
7354
- ".arcbridge",
7355
- "plan",
7356
- "tasks",
7357
- `${phaseId}.yaml`
7358
- );
7359
- if (!existsSync8(taskPath)) {
7360
- return { success: true };
7361
- }
7362
- const raw = readFileSync8(taskPath, "utf-8");
7363
- const result = TaskFileSchema.safeParse(parse2(raw));
7364
- if (!result.success) {
7400
+ const readResult = readTaskFile(projectRoot, phaseId);
7401
+ if ("error" in readResult) {
7402
+ if (readResult.error === "not-found") {
7403
+ return { success: true };
7404
+ }
7365
7405
  return {
7366
7406
  success: false,
7367
7407
  warning: `Could not parse ${phaseId}.yaml \u2014 task may reappear after reindex`
7368
7408
  };
7369
7409
  }
7370
- const taskFile = result.data;
7410
+ const { data: taskFile, path: taskPath } = readResult;
7371
7411
  const before = taskFile.tasks.length;
7372
7412
  taskFile.tasks = taskFile.tasks.filter((t) => t.id !== taskId);
7373
7413
  if (taskFile.tasks.length === before) {
@@ -7384,19 +7424,14 @@ function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
7384
7424
  }
7385
7425
  function deletePhaseFromYaml(projectRoot, phaseId) {
7386
7426
  try {
7387
- const phasesPath = join15(projectRoot, ".arcbridge", "plan", "phases.yaml");
7388
- if (!existsSync8(phasesPath)) {
7389
- return { success: false, warning: "phases.yaml not found" };
7390
- }
7391
- const raw = readFileSync8(phasesPath, "utf-8");
7392
- const result = PhasesFileSchema.safeParse(parse2(raw));
7393
- if (!result.success) {
7427
+ const readResult = readPhasesFile(projectRoot);
7428
+ if ("error" in readResult) {
7394
7429
  return {
7395
7430
  success: false,
7396
- warning: "Could not parse phases.yaml \u2014 phase may reappear after reindex"
7431
+ warning: readResult.error === "not-found" ? "phases.yaml not found" : "Could not parse phases.yaml \u2014 phase may reappear after reindex"
7397
7432
  };
7398
7433
  }
7399
- const phasesFile = result.data;
7434
+ const { data: phasesFile, path: phasesPath } = readResult;
7400
7435
  const before = phasesFile.phases.length;
7401
7436
  phasesFile.phases = phasesFile.phases.filter((p) => p.id !== phaseId);
7402
7437
  if (phasesFile.phases.length === before) {
@@ -8384,6 +8419,7 @@ export {
8384
8419
  CURRENT_SCHEMA_VERSION,
8385
8420
  PhaseSchema,
8386
8421
  PhasesFileSchema,
8422
+ QUALITY_PRIORITIES_DESCRIPTION,
8387
8423
  QualityCategorySchema,
8388
8424
  QualityPrioritySchema,
8389
8425
  QualityScenarioSchema,