@akanjs/cli 2.3.9-rc.3 → 2.3.9-rc.5

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/index.js CHANGED
@@ -3394,28 +3394,28 @@ class SysExecutor extends Executor {
3394
3394
  return scalarModules;
3395
3395
  }
3396
3396
  async getViewComponents() {
3397
- const viewComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.View.tsx`).exists());
3397
+ const viewComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.View.tsx`).exists());
3398
3398
  return viewComponents;
3399
3399
  }
3400
3400
  async getUnitComponents() {
3401
- const unitComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`).exists());
3401
+ const unitComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Unit.tsx`).exists());
3402
3402
  return unitComponents;
3403
3403
  }
3404
3404
  async getTemplateComponents() {
3405
- const templateComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`).exists());
3405
+ const templateComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Template.tsx`).exists());
3406
3406
  return templateComponents;
3407
3407
  }
3408
3408
  async getViewsSourceCode() {
3409
3409
  const viewComponents = await this.getViewComponents();
3410
- return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${viewComponent}.View.tsx`)));
3410
+ return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`)));
3411
3411
  }
3412
3412
  async getUnitsSourceCode() {
3413
3413
  const unitComponents = await this.getUnitComponents();
3414
- return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${unitComponent}.Unit.tsx`)));
3414
+ return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`)));
3415
3415
  }
3416
3416
  async getTemplatesSourceCode() {
3417
3417
  const templateComponents = await this.getTemplateComponents();
3418
- return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${templateComponent}.Template.tsx`)));
3418
+ return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`)));
3419
3419
  }
3420
3420
  async getScalarConstantFiles() {
3421
3421
  const scalarModules = await this.getScalarModules();
@@ -4776,238 +4776,11 @@ class AkanAppHost {
4776
4776
  // pkgs/@akanjs/devkit/akanContext.ts
4777
4777
  import { readdir } from "fs/promises";
4778
4778
  import path10 from "path";
4779
- import { capitalize as capitalize3 } from "akanjs/common";
4779
+ import { capitalize as capitalize4 } from "akanjs/common";
4780
4780
 
4781
- // pkgs/@akanjs/devkit/workflow/index.ts
4782
- import { capitalize as capitalize2 } from "akanjs/common";
4781
+ // pkgs/@akanjs/devkit/workflow/utils.ts
4783
4782
  var jsonText = (value, { trailingNewline = true } = {}) => `${JSON.stringify(value, null, 2)}${trailingNewline ? `
4784
4783
  ` : ""}`;
4785
- var surfaceModes = new Set(["infer", "include", "skip"]);
4786
- var parseStringList = (value) => {
4787
- if (Array.isArray(value)) {
4788
- const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
4789
- return values.length === value.length ? values : null;
4790
- }
4791
- if (typeof value !== "string")
4792
- return null;
4793
- return value.split(",").map((item) => item.trim()).filter(Boolean);
4794
- };
4795
- var normalizeInputValue = (name, spec, value) => {
4796
- if (spec.type === "string")
4797
- return typeof value === "string" && value.length > 0 ? value : null;
4798
- if (spec.type === "string-list") {
4799
- const values = parseStringList(value);
4800
- return values && values.length > 0 ? values : null;
4801
- }
4802
- if (typeof value === "string" && surfaceModes.has(value))
4803
- return value;
4804
- throw new Error(`Unsupported workflow input value for ${name}`);
4805
- };
4806
- var listWorkflowSpecs = (specs) => [...specs].sort((a, b) => a.name.localeCompare(b.name));
4807
- var getWorkflowSpec = (specs, name) => specs.find((spec) => spec.name === name) ?? null;
4808
- var compactWorkflowInputs = (inputs) => Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
4809
- var addFieldComponentForType = (typeName) => {
4810
- const normalizedType = normalizeFieldType(typeName);
4811
- if (normalizedType === "Boolean")
4812
- return "Field.ToggleSelect";
4813
- if (normalizedType === "Date")
4814
- return "Field.Date";
4815
- if (normalizedType === "Int" || normalizedType === "Float")
4816
- return "Field.ToggleSelect or Field.Text";
4817
- if (typeName.toLowerCase() === "enum")
4818
- return "Field.ToggleSelect";
4819
- return "Field.Text";
4820
- };
4821
- var createAddFieldRecommendations = (inputs) => {
4822
- const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
4823
- const module = typeof inputs.module === "string" ? inputs.module : "<module>";
4824
- const field = typeof inputs.field === "string" ? inputs.field : "<field>";
4825
- const typeName = typeof inputs.type === "string" ? inputs.type : null;
4826
- if (!typeName)
4827
- return [];
4828
- const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
4829
- const constantPath = `*/lib/${module}/${module}.constant.ts`;
4830
- const dictionaryPath = `*/lib/${module}/${module}.dictionary.ts`;
4831
- const templatePath = `*/lib/${module}/${capitalize2(module)}.Template.tsx`;
4832
- return [
4833
- ...normalizedType === "Int" || normalizedType === "Float" ? [
4834
- {
4835
- code: "add-field-import",
4836
- kind: "import",
4837
- target: constantPath,
4838
- confidence: "high",
4839
- message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`
4840
- }
4841
- ] : [],
4842
- {
4843
- code: "add-field-placement-constant",
4844
- kind: "placement",
4845
- target: constantPath,
4846
- confidence: "high",
4847
- message: `Insert ${field}: field(${normalizedType}) in ${capitalize2(module)}Input.`
4848
- },
4849
- {
4850
- code: "add-field-placement-dictionary",
4851
- kind: "placement",
4852
- target: dictionaryPath,
4853
- confidence: "high",
4854
- message: `Add dictionary labels for ${module}.${field}.`
4855
- },
4856
- {
4857
- code: "add-field-component",
4858
- kind: "ui-component",
4859
- target: templatePath,
4860
- confidence: normalizedType === "Int" || normalizedType === "Float" ? "medium" : "high",
4861
- message: `Recommended UI component for ${field} (${normalizedType}): ${addFieldComponentForType(typeName)}.`
4862
- },
4863
- {
4864
- code: "add-field-ui-manual-review",
4865
- kind: "manual-action",
4866
- target: templatePath,
4867
- action: `Review ${app}:${module} Template/Unit/View/Store surfaces and add ${field} only where the existing pattern is clear.`,
4868
- confidence: "medium",
4869
- message: "UI surface edits are planned as manual-review unless a safe existing field-list pattern is detected."
4870
- }
4871
- ];
4872
- };
4873
- var createWorkflowPlanRecommendations = (spec, inputs) => [
4874
- {
4875
- code: "workflow-apply-first",
4876
- kind: "auto-apply",
4877
- confidence: "high",
4878
- action: "Call apply_workflow with the MCP planPath before editing source files directly.",
4879
- message: `Apply the ${spec.name} plan through apply_workflow when MCP returns planPath or next.tool=apply_workflow.`
4880
- },
4881
- {
4882
- code: "workflow-validate-apply-report",
4883
- kind: "validation",
4884
- confidence: "high",
4885
- action: "After apply_workflow, call run_validation with validationTarget when present; otherwise use applyReportPath.",
4886
- message: "Validate the apply report artifact so diagnostics and recommendations follow the actual apply result."
4887
- },
4888
- ...spec.name === "add-field" ? createAddFieldRecommendations(inputs) : []
4889
- ];
4890
- var createWorkflowPlan = (spec, rawInputs) => {
4891
- const inputs = {};
4892
- const diagnostics = [];
4893
- for (const [name, inputSpec] of Object.entries(spec.inputs)) {
4894
- const rawValue = rawInputs[name];
4895
- if (rawValue === undefined || rawValue === null || rawValue === "") {
4896
- if (inputSpec.required) {
4897
- diagnostics.push({
4898
- severity: "error",
4899
- code: "workflow-input-missing",
4900
- input: name,
4901
- message: `Workflow ${spec.name} requires input "${name}".`
4902
- });
4903
- }
4904
- continue;
4905
- }
4906
- const value = normalizeInputValue(name, inputSpec, rawValue);
4907
- if (value === null) {
4908
- diagnostics.push({
4909
- severity: "error",
4910
- code: "workflow-input-invalid",
4911
- input: name,
4912
- message: `Workflow ${spec.name} input "${name}" must be ${inputSpec.type}.`
4913
- });
4914
- continue;
4915
- }
4916
- if (inputSpec.allowedValues && typeof value === "string" && !inputSpec.allowedValues.includes(value)) {
4917
- diagnostics.push({
4918
- severity: "error",
4919
- code: "workflow-input-not-allowed",
4920
- input: name,
4921
- message: `Workflow ${spec.name} input "${name}" must be one of: ${inputSpec.allowedValues.join(", ")}.`
4922
- });
4923
- continue;
4924
- }
4925
- inputs[name] = value;
4926
- }
4927
- const fieldType = inputs.type;
4928
- if (spec.name === "add-field" && typeof fieldType === "string" && (fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")) {
4929
- diagnostics.push({
4930
- severity: "error",
4931
- code: "primitive-field-type-unsupported",
4932
- input: "type",
4933
- message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`
4934
- });
4935
- }
4936
- return {
4937
- schemaVersion: 1,
4938
- workflow: spec.name,
4939
- mode: "plan",
4940
- inputs,
4941
- optionalSurfaces: spec.optionalSurfaces ?? {},
4942
- steps: spec.steps,
4943
- predictedChanges: spec.predictedChanges,
4944
- validation: spec.validation,
4945
- diagnostics,
4946
- recommendations: createWorkflowPlanRecommendations(spec, inputs),
4947
- requiresApproval: true
4948
- };
4949
- };
4950
- var renderWorkflowList = (specs) => [
4951
- "# Akan Workflows",
4952
- "",
4953
- ...specs.flatMap((spec) => [`- \`${spec.name}\`: ${spec.description}`, ` - When: ${spec.whenToUse}`]),
4954
- ""
4955
- ].join(`
4956
- `);
4957
- var renderWorkflowExplain = (spec) => [
4958
- `# Workflow: ${spec.name}`,
4959
- "",
4960
- spec.description,
4961
- "",
4962
- "## When To Use",
4963
- spec.whenToUse,
4964
- "",
4965
- "## Inputs",
4966
- ...Object.entries(spec.inputs).map(([name, input2]) => `- \`${name}\`${input2.required ? " (required)" : ""}: ${input2.description}${input2.allowedValues ? ` Allowed: ${input2.allowedValues.join(", ")}.` : ""}`),
4967
- "",
4968
- "## Optional Surfaces",
4969
- ...Object.entries(spec.optionalSurfaces ?? {}).map(([name, mode]) => `- \`${name}\`: ${mode}`),
4970
- "",
4971
- "## Steps",
4972
- ...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
4973
- "",
4974
- "## Validation",
4975
- ...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
4976
- ""
4977
- ].join(`
4978
- `);
4979
- var renderWorkflowPlan = (plan) => [
4980
- `# Workflow Plan: ${plan.workflow}`,
4981
- "",
4982
- `- Mode: ${plan.mode}`,
4983
- `- Requires approval: ${plan.requiresApproval}`,
4984
- "",
4985
- "## Inputs",
4986
- ...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
4987
- "",
4988
- "## Optional Surfaces",
4989
- ...Object.entries(plan.optionalSurfaces).map(([name, mode]) => `- \`${name}\`: ${mode}`),
4990
- "",
4991
- "## Steps",
4992
- ...plan.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
4993
- "",
4994
- "## Predicted Changes",
4995
- ...plan.predictedChanges.map((change) => {
4996
- const scope = change.applyScope ? ` (${change.applyScope})` : "";
4997
- return `- \`${change.action}\`${scope} ${change.target}: ${change.reason}`;
4998
- }),
4999
- "",
5000
- "## Validation",
5001
- ...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
5002
- "",
5003
- "## Diagnostics",
5004
- ...plan.diagnostics.length ? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
5005
- "",
5006
- "## Recommendations",
5007
- ...plan.recommendations.length ? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`) : ["- none"],
5008
- ""
5009
- ].join(`
5010
- `);
5011
4784
  var workflowStatus = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed";
5012
4785
  var commandStatus = (commands) => commands.some((command) => command.status === "failed") ? "failed" : "passed";
5013
4786
  var uniqueBy = (values, key) => {
@@ -5020,6 +4793,9 @@ var uniqueBy = (values, key) => {
5020
4793
  return true;
5021
4794
  });
5022
4795
  };
4796
+ var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!diagnostic);
4797
+
4798
+ // pkgs/@akanjs/devkit/workflow/artifacts.ts
5023
4799
  var createWorkflowApplyReport = ({
5024
4800
  workflow,
5025
4801
  mode,
@@ -5102,6 +4878,66 @@ var readWorkflowRunArtifact = async (workspace, runId) => {
5102
4878
  throw new Error(`Workflow run artifact does not exist: ${artifactPath}`);
5103
4879
  return await workspace.readJson(artifactPath);
5104
4880
  };
4881
+ var failedCommandScopes = (commands) => commands.filter((command) => command.status === "failed").map((command) => command.failureScope ?? "unknown");
4882
+ var errorDiagnosticScopes = (diagnostics) => diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.failureScope ?? (diagnostic.scope === "baseline" ? "workspace-config" : diagnostic.scope === "workflow" ? "source-change" : "unknown"));
4883
+ var hasScopeFailure = (scopes, scope, diagnostics = []) => scopes.includes(scope) || diagnostics.some((diagnostic) => diagnostic.severity === "error" && diagnostic.failureScope === scope);
4884
+ var statusForScope = (commands, diagnostics, scopes, expectedScope) => {
4885
+ const hasCommands = commands.length > 0 || diagnostics.length > 0;
4886
+ if (!hasCommands)
4887
+ return "unknown";
4888
+ return hasScopeFailure(scopes, expectedScope, diagnostics) ? "failed" : "passed";
4889
+ };
4890
+ var statusForValidationKind = (commands, kind) => {
4891
+ const matching = commands.filter((command) => command.kind === kind);
4892
+ if (matching.length === 0)
4893
+ return "unknown";
4894
+ return matching.some((command) => command.status === "failed") ? "failed" : "passed";
4895
+ };
4896
+ var createKnownBlockers = (commands, diagnostics) => {
4897
+ const commandBlockers = commands.filter((command) => command.status === "failed" && (command.failureScope === "workspace-config" || command.failureScope === "environment")).map((command) => ({
4898
+ code: `workflow-validation-${command.failureScope}`,
4899
+ message: `${command.failureScope === "environment" ? "Environment" : "Workspace configuration"} blocker: ${command.command}`,
4900
+ failureScope: command.failureScope ?? "unknown",
4901
+ command: command.command,
4902
+ kind: command.kind,
4903
+ count: 1
4904
+ }));
4905
+ const diagnosticBlockers = diagnostics.filter((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "workspace-config" || diagnostic.failureScope === "environment" || diagnostic.scope === "baseline")).map((diagnostic) => ({
4906
+ code: diagnostic.code,
4907
+ message: diagnostic.message,
4908
+ failureScope: diagnostic.failureScope ?? "workspace-config",
4909
+ command: diagnostic.command,
4910
+ kind: diagnostic.kind,
4911
+ count: 1
4912
+ }));
4913
+ const grouped = new Map;
4914
+ for (const blocker of [...commandBlockers, ...diagnosticBlockers]) {
4915
+ const key = `${blocker.failureScope}:${blocker.code}:${blocker.command ?? ""}:${blocker.message}`;
4916
+ const existing = grouped.get(key);
4917
+ if (existing)
4918
+ existing.count += blocker.count;
4919
+ else
4920
+ grouped.set(key, blocker);
4921
+ }
4922
+ return [...grouped.values()];
4923
+ };
4924
+ var createValidationStatuses = (commands, diagnostics) => {
4925
+ const scopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(diagnostics)];
4926
+ const sourceStatus = statusForScope(commands, diagnostics, scopes, "source-change");
4927
+ const workspaceStatus = hasScopeFailure(scopes, "workspace-config", diagnostics) || hasScopeFailure(scopes, "environment", diagnostics) ? "failed" : commands.length || diagnostics.length ? "passed" : "unknown";
4928
+ const overallStatus = hasScopeFailure(scopes, "source-change", diagnostics) ? "failed" : hasScopeFailure(scopes, "workspace-config", diagnostics) ? "blocked-by-workspace-config" : hasScopeFailure(scopes, "environment", diagnostics) ? "blocked-by-environment" : workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed";
4929
+ return {
4930
+ sourceStatus,
4931
+ workspaceStatus,
4932
+ overallStatus,
4933
+ summary: {
4934
+ sourceChange: sourceStatus,
4935
+ generatedSync: statusForValidationKind(commands, "sync"),
4936
+ workspaceConfig: statusForScope(commands, diagnostics, scopes, "workspace-config"),
4937
+ environment: statusForScope(commands, diagnostics, scopes, "environment")
4938
+ }
4939
+ };
4940
+ };
5105
4941
  var createWorkflowValidationRunReport = async ({
5106
4942
  runId = createWorkflowRunId("validation"),
5107
4943
  workflow,
@@ -5128,15 +4964,20 @@ var createWorkflowValidationRunReport = async ({
5128
4964
  failureScope: result.failureScope
5129
4965
  }
5130
4966
  ] : []);
4967
+ const reportDiagnostics = [...diagnostics, ...commandDiagnostics];
4968
+ const scopedDiagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
4969
+ const statuses = createValidationStatuses(results, scopedDiagnostics);
5131
4970
  return {
5132
4971
  schemaVersion: 1,
5133
4972
  runId,
5134
4973
  workflow,
5135
4974
  mode: "validate",
5136
4975
  source,
5137
- status: workflowStatus([...diagnostics, ...commandDiagnostics]) === "failed" ? "failed" : commandStatus(results),
4976
+ status: statuses.overallStatus === "passed" ? "passed" : "failed",
4977
+ ...statuses,
4978
+ knownBlockers: createKnownBlockers(results, scopedDiagnostics),
5138
4979
  commands: results,
5139
- diagnostics: [...diagnostics, ...commandDiagnostics],
4980
+ diagnostics: reportDiagnostics,
5140
4981
  baselineDiagnostics,
5141
4982
  workflowDiagnostics,
5142
4983
  repairActions: uniqueBy(repairActions, (action) => action.command),
@@ -5180,25 +5021,512 @@ var createDryRunWorkflowApplyReport = (plan) => {
5180
5021
  plan
5181
5022
  });
5182
5023
  };
5183
- var workflowStepKey = (workflow, stepId) => `${workflow}:${stepId}`;
5184
- var primitiveReportToWorkflowStepResult = (report) => ({
5185
- changedFiles: report.changedFiles,
5186
- generatedFiles: report.generatedFiles,
5187
- commands: report.validationCommands,
5188
- diagnostics: report.diagnostics,
5189
- recommendations: [],
5190
- nextActions: report.nextActions
5024
+ var createRepairReport = ({
5025
+ command,
5026
+ kind,
5027
+ target = null,
5028
+ diagnostics = [],
5029
+ repairActions = [],
5030
+ nextActions = [],
5031
+ commands = [],
5032
+ generatedFiles = [],
5033
+ syncedAt
5034
+ }) => ({
5035
+ schemaVersion: 1,
5036
+ command,
5037
+ kind,
5038
+ target,
5039
+ status: workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed",
5040
+ diagnostics,
5041
+ repairActions: uniqueBy(repairActions, (action) => action.command),
5042
+ nextActions: uniqueBy(nextActions, (action) => action.command),
5043
+ commands,
5044
+ generatedFiles,
5045
+ ...syncedAt ? { syncedAt } : {}
5191
5046
  });
5192
- var workflowStringInput = (value) => typeof value === "string" ? value : null;
5193
- var workflowStringListInput = (value) => Array.isArray(value) ? value.join(",") : null;
5194
- var resolveWorkflowSys = async (workspace, target) => {
5195
- if (!target)
5196
- return null;
5197
- const [apps, libs] = await workspace.getSyss();
5198
- if (apps.includes(target))
5199
- return AppExecutor.from(workspace, target);
5200
- if (libs.includes(target))
5201
- return LibExecutor.from(workspace, target);
5047
+ // pkgs/@akanjs/devkit/workflow/executor.ts
5048
+ import { capitalize as capitalize2 } from "akanjs/common";
5049
+
5050
+ // pkgs/@akanjs/devkit/workflow/primitive.ts
5051
+ var createPrimitiveWriteReport = ({
5052
+ command,
5053
+ status,
5054
+ changedFiles = [],
5055
+ generatedFiles = [],
5056
+ validationCommands = [],
5057
+ diagnostics = [],
5058
+ nextActions = []
5059
+ }) => ({
5060
+ schemaVersion: 1,
5061
+ command,
5062
+ status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
5063
+ changedFiles,
5064
+ generatedFiles,
5065
+ validationCommands,
5066
+ diagnostics,
5067
+ nextActions
5068
+ });
5069
+ var renderPrimitiveWriteReport = (report) => [
5070
+ `# Primitive Write: ${report.command}`,
5071
+ "",
5072
+ `- Status: ${report.status}`,
5073
+ "",
5074
+ "## Changed Files",
5075
+ ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
5076
+ "",
5077
+ "## Generated Files",
5078
+ ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
5079
+ "",
5080
+ "## Validation Commands",
5081
+ ...report.validationCommands.length ? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`) : ["- none"],
5082
+ "",
5083
+ "## Diagnostics",
5084
+ ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
5085
+ "",
5086
+ "## Next Actions",
5087
+ ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
5088
+ ""
5089
+ ].join(`
5090
+ `);
5091
+ var renderPrimitiveReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
5092
+
5093
+ // pkgs/@akanjs/devkit/workflow/source.ts
5094
+ var getSysRoot = (sys2) => `${sys2.type}s/${sys2.name}`;
5095
+ var sourceFile = (sys2, path10, action, reason) => ({
5096
+ path: `${getSysRoot(sys2)}/${path10}`,
5097
+ action,
5098
+ reason
5099
+ });
5100
+ var moduleComponentName = (moduleName) => `${moduleName.slice(0, 1).toUpperCase()}${moduleName.slice(1)}`;
5101
+ var moduleSourcePaths = (moduleName) => {
5102
+ const componentName = moduleComponentName(moduleName);
5103
+ return {
5104
+ abstract: `lib/${moduleName}/${moduleName}.abstract.md`,
5105
+ constant: `lib/${moduleName}/${moduleName}.constant.ts`,
5106
+ dictionary: `lib/${moduleName}/${moduleName}.dictionary.ts`,
5107
+ service: `lib/${moduleName}/${moduleName}.service.ts`,
5108
+ signal: `lib/${moduleName}/${moduleName}.signal.ts`,
5109
+ store: `lib/${moduleName}/${moduleName}.store.ts`,
5110
+ template: `lib/${moduleName}/${componentName}.Template.tsx`,
5111
+ unit: `lib/${moduleName}/${componentName}.Unit.tsx`,
5112
+ util: `lib/${moduleName}/${componentName}.Util.tsx`,
5113
+ view: `lib/${moduleName}/${componentName}.View.tsx`,
5114
+ zone: `lib/${moduleName}/${componentName}.Zone.tsx`
5115
+ };
5116
+ };
5117
+ var generatedFilesForSync = (sys2, reason = "Generated files may change after sync.") => generatedFilePathsForTarget(getSysRoot(sys2), reason);
5118
+ var validationCommandsForTarget = (target) => [
5119
+ { command: `akan sync ${target}`, reason: "Refresh generated Akan files from source conventions." },
5120
+ { command: `akan lint ${target}`, reason: "Validate formatting, imports, and static lint rules." }
5121
+ ];
5122
+ var nextActionsForTarget = (target) => [
5123
+ { command: `akan sync ${target}`, reason: "Refresh generated Akan files after source changes." },
5124
+ { command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." }
5125
+ ];
5126
+ var createPassedPrimitiveReport = ({
5127
+ command,
5128
+ changedFiles,
5129
+ generatedFiles,
5130
+ target,
5131
+ nextActions
5132
+ }) => createPrimitiveWriteReport({
5133
+ command,
5134
+ changedFiles,
5135
+ generatedFiles: generatedFiles ?? [],
5136
+ validationCommands: validationCommandsForTarget(target),
5137
+ diagnostics: [],
5138
+ nextActions: nextActions ?? nextActionsForTarget(target)
5139
+ });
5140
+ var scalarChangedFiles = (sys2, scalarName, files) => Object.values(files).map((file) => sourceFile(sys2, `lib/__scalar/${scalarName}/${file.filename}`, "create", "Scalar source file was created."));
5141
+ var titleize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
5142
+ var lowerlize = (value) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
5143
+ var koLabels = {
5144
+ amount: "\uAE08\uC561",
5145
+ budget: "\uC608\uC0B0",
5146
+ category: "\uCE74\uD14C\uACE0\uB9AC",
5147
+ content: "\uB0B4\uC6A9",
5148
+ count: "\uAC1C\uC218",
5149
+ createdAt: "\uC0DD\uC131\uC77C",
5150
+ date: "\uB0A0\uC9DC",
5151
+ description: "\uC124\uBA85",
5152
+ due: "\uB9C8\uAC10\uC77C",
5153
+ dueAt: "\uB9C8\uAC10\uC77C",
5154
+ email: "\uC774\uBA54\uC77C",
5155
+ enabled: "\uD65C\uC131\uD654",
5156
+ endAt: "\uC885\uB8CC\uC77C",
5157
+ id: "ID",
5158
+ name: "\uC774\uB984",
5159
+ owner: "\uB2F4\uB2F9\uC790",
5160
+ priority: "\uC6B0\uC120\uC21C\uC704",
5161
+ project: "\uD504\uB85C\uC81D\uD2B8",
5162
+ rating: "\uD3C9\uC810",
5163
+ startAt: "\uC2DC\uC791\uC77C",
5164
+ status: "\uC0C1\uD0DC",
5165
+ title: "\uC81C\uBAA9",
5166
+ updatedAt: "\uC218\uC815\uC77C"
5167
+ };
5168
+ var splitFieldWords = (fieldName) => fieldName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").split(/\s+/).map((word) => word.trim()).filter(Boolean);
5169
+ var koLabelForField = (fieldName) => {
5170
+ if (koLabels[fieldName])
5171
+ return koLabels[fieldName];
5172
+ const words = splitFieldWords(fieldName);
5173
+ const translated = words.map((word) => koLabels[word] ?? koLabels[lowerlize(word)] ?? null);
5174
+ return translated.every(Boolean) ? translated.join(" ") : null;
5175
+ };
5176
+ var bilingualLabelForField = (fieldName) => {
5177
+ const en = titleize(fieldName);
5178
+ return { en, ko: koLabelForField(fieldName) ?? en };
5179
+ };
5180
+ var bilingualDescriptionForField = (fieldName) => {
5181
+ const label = bilingualLabelForField(fieldName);
5182
+ return {
5183
+ en: `Enter ${label.en.toLowerCase()}.`,
5184
+ ko: `${label.ko} \uAC12\uC744 \uC785\uB825\uD569\uB2C8\uB2E4.`
5185
+ };
5186
+ };
5187
+ var normalizeFieldType = (typeName) => {
5188
+ const normalizedTypes = {
5189
+ string: "String",
5190
+ boolean: "Boolean",
5191
+ date: "Date",
5192
+ int: "Int",
5193
+ integer: "Int",
5194
+ float: "Float",
5195
+ double: "Float",
5196
+ decimal: "Float"
5197
+ };
5198
+ return normalizedTypes[typeName.toLowerCase()] ?? typeName;
5199
+ };
5200
+ var ensureBaseTypeImport = (content, typeName) => {
5201
+ if (typeName !== "Int" && typeName !== "Float")
5202
+ return content;
5203
+ if (new RegExp(`import \\{[^}]*\\b${typeName}\\b[^}]*\\} from "akanjs/base";`).test(content))
5204
+ return content;
5205
+ const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
5206
+ if (baseImport) {
5207
+ const names = baseImport[1].split(",").map((name) => name.trim()).filter(Boolean);
5208
+ return content.replace(baseImport[0], `import { ${[...names, typeName].sort().join(", ")} } from "akanjs/base";`);
5209
+ }
5210
+ return `import { ${typeName} } from "akanjs/base";
5211
+ ${content}`;
5212
+ };
5213
+ var numericDefault = (typeName, rawDefault) => {
5214
+ if (typeof rawDefault === "number") {
5215
+ if (!Number.isFinite(rawDefault))
5216
+ return null;
5217
+ if (typeName === "Int" && !Number.isInteger(rawDefault))
5218
+ return null;
5219
+ return String(rawDefault);
5220
+ }
5221
+ if (typeof rawDefault !== "string")
5222
+ return null;
5223
+ const trimmed = rawDefault.trim();
5224
+ if (!trimmed || !Number.isFinite(Number(trimmed)))
5225
+ return null;
5226
+ if (typeName === "Int" && !/^-?\d+$/.test(trimmed))
5227
+ return null;
5228
+ return trimmed;
5229
+ };
5230
+ var booleanDefault = (rawDefault) => {
5231
+ if (typeof rawDefault === "boolean")
5232
+ return String(rawDefault);
5233
+ if (typeof rawDefault !== "string")
5234
+ return null;
5235
+ const lowered = rawDefault.trim().toLowerCase();
5236
+ return lowered === "true" || lowered === "false" ? lowered : null;
5237
+ };
5238
+ var dateDefault = (rawDefault) => {
5239
+ if (typeof rawDefault === "number" && Number.isFinite(rawDefault))
5240
+ return `new Date(${rawDefault})`;
5241
+ if (typeof rawDefault !== "string")
5242
+ return null;
5243
+ const trimmed = rawDefault.trim();
5244
+ if (!trimmed)
5245
+ return null;
5246
+ if (trimmed === "now")
5247
+ return "new Date()";
5248
+ if (!Number.isNaN(Date.parse(trimmed)))
5249
+ return `new Date(${JSON.stringify(trimmed)})`;
5250
+ return null;
5251
+ };
5252
+ var coerceFieldDefault = (typeName, defaultValue, options = {}) => {
5253
+ const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
5254
+ if (defaultValue === undefined || defaultValue === null || defaultValue === "") {
5255
+ return { expression: null, normalized: false, normalizedType };
5256
+ }
5257
+ if (normalizedType === "Int" || normalizedType === "Float") {
5258
+ const expression = numericDefault(normalizedType, defaultValue);
5259
+ if (expression !== null)
5260
+ return { expression, normalized: typeof defaultValue === "string", normalizedType };
5261
+ return {
5262
+ expression: null,
5263
+ normalized: false,
5264
+ normalizedType,
5265
+ diagnostic: {
5266
+ severity: "error",
5267
+ code: "primitive-default-value-invalid",
5268
+ input: "default",
5269
+ failureScope: "source-change",
5270
+ message: `Default value for ${normalizedType} must be a numeric literal. Received: ${JSON.stringify(defaultValue)}.`
5271
+ }
5272
+ };
5273
+ }
5274
+ if (normalizedType === "Boolean") {
5275
+ const expression = booleanDefault(defaultValue);
5276
+ if (expression !== null)
5277
+ return { expression, normalized: typeof defaultValue === "string", normalizedType };
5278
+ return {
5279
+ expression: null,
5280
+ normalized: false,
5281
+ normalizedType,
5282
+ diagnostic: {
5283
+ severity: "error",
5284
+ code: "primitive-default-value-invalid",
5285
+ input: "default",
5286
+ failureScope: "source-change",
5287
+ message: `Default value for Boolean must be true or false. Received: ${JSON.stringify(defaultValue)}.`
5288
+ }
5289
+ };
5290
+ }
5291
+ if (normalizedType === "Date") {
5292
+ const expression = dateDefault(defaultValue);
5293
+ if (expression !== null)
5294
+ return { expression, normalized: true, normalizedType };
5295
+ return {
5296
+ expression: null,
5297
+ normalized: false,
5298
+ normalizedType,
5299
+ diagnostic: {
5300
+ severity: "error",
5301
+ code: "primitive-default-value-invalid",
5302
+ input: "default",
5303
+ failureScope: "source-change",
5304
+ message: `Default value for Date must be "now", a timestamp, or a parseable date string. Received: ${JSON.stringify(defaultValue)}.`
5305
+ }
5306
+ };
5307
+ }
5308
+ if (normalizedType === "enum") {
5309
+ if (typeof defaultValue === "string" && options.enumValues?.includes(defaultValue)) {
5310
+ return { expression: JSON.stringify(defaultValue), normalized: false, normalizedType };
5311
+ }
5312
+ return {
5313
+ expression: null,
5314
+ normalized: false,
5315
+ normalizedType,
5316
+ diagnostic: {
5317
+ severity: "error",
5318
+ code: "primitive-default-value-invalid",
5319
+ input: "default",
5320
+ failureScope: "source-change",
5321
+ message: `Default value for enum must be one of: ${(options.enumValues ?? []).join(", ")}. Received: ${JSON.stringify(defaultValue)}.`
5322
+ }
5323
+ };
5324
+ }
5325
+ return {
5326
+ expression: JSON.stringify(String(defaultValue)),
5327
+ normalized: typeof defaultValue !== "string",
5328
+ normalizedType
5329
+ };
5330
+ };
5331
+ var fieldExpression = (typeName, defaultValue, options = {}) => {
5332
+ const typeExpression = normalizeFieldType(typeName);
5333
+ const defaultExpression = coerceFieldDefault(options.enumValues ? "enum" : typeExpression, defaultValue, options).expression;
5334
+ const defaultOption = defaultExpression ? `, { default: ${defaultExpression} }` : "";
5335
+ return `field(${typeExpression}${defaultOption})`;
5336
+ };
5337
+ var insertIntoObject = (content, className, line) => {
5338
+ const classIndex = content.indexOf(`export class ${className} extends via`);
5339
+ if (classIndex < 0)
5340
+ return null;
5341
+ const objectEndIndex = content.indexOf("}))", classIndex);
5342
+ if (objectEndIndex < 0)
5343
+ return null;
5344
+ const prefix = content.slice(0, objectEndIndex);
5345
+ const suffix = content.slice(objectEndIndex);
5346
+ const insertion = prefix.endsWith(`
5347
+ `) ? ` ${line}
5348
+ ` : `
5349
+ ${line}
5350
+ `;
5351
+ return `${prefix}${insertion}${suffix}`;
5352
+ };
5353
+ var insertLightProjectionField = (content, moduleClassName, fieldName) => {
5354
+ const classIndex = content.indexOf(`export class Light${moduleClassName} extends via`);
5355
+ if (classIndex < 0)
5356
+ return null;
5357
+ const arrayMatch = /\[([\s\S]*?)\]\s+as const/.exec(content.slice(classIndex));
5358
+ if (!arrayMatch || arrayMatch.index === undefined)
5359
+ return null;
5360
+ const arrayStart = classIndex + arrayMatch.index;
5361
+ const arrayEnd = arrayStart + arrayMatch[0].length;
5362
+ const fields = [...arrayMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]).filter(Boolean);
5363
+ if (fields.includes(fieldName))
5364
+ return content;
5365
+ const nextFields = [...fields, fieldName];
5366
+ const nextArray = nextFields.length === 0 ? "[] as const" : `[
5367
+ ${nextFields.map((field) => ` ${JSON.stringify(field)},`).join(`
5368
+ `)}
5369
+ ] as const`;
5370
+ return `${content.slice(0, arrayStart)}${nextArray}${content.slice(arrayEnd)}`;
5371
+ };
5372
+ var insertTemplateField = ({
5373
+ content,
5374
+ moduleName,
5375
+ moduleClassName,
5376
+ fieldName,
5377
+ component
5378
+ }) => {
5379
+ if (content.includes(`l("${moduleName}.${fieldName}")`) || content.includes(`.${fieldName}`))
5380
+ return content;
5381
+ const layoutEndIndex = content.indexOf(" </Layout.Template>");
5382
+ if (layoutEndIndex < 0)
5383
+ return null;
5384
+ const formName = `${moduleName}Form`;
5385
+ if (!content.includes(`const ${formName} = st.use.${moduleName}Form();`))
5386
+ return null;
5387
+ const fieldSetter = `st.do.set${moduleComponentName(fieldName)}On${moduleClassName}`;
5388
+ const fieldBlock = ` <${component}
5389
+ label={l("${moduleName}.${fieldName}")}
5390
+ desc={l("${moduleName}.${fieldName}.desc")}
5391
+ value={${formName}.${fieldName}}
5392
+ onChange={${fieldSetter}}
5393
+ />
5394
+ `;
5395
+ return `${content.slice(0, layoutEndIndex)}${fieldBlock}${content.slice(layoutEndIndex)}`;
5396
+ };
5397
+ var ensureEnumImport = (content) => {
5398
+ if (content.includes("enumOf"))
5399
+ return content;
5400
+ const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
5401
+ if (baseImport) {
5402
+ const names = baseImport[1]?.split(",").map((name) => name.trim()) ?? [];
5403
+ return content.replace(baseImport[0], `import { ${[...names, "enumOf"].sort().join(", ")} } from "akanjs/base";`);
5404
+ }
5405
+ return `import { enumOf } from "akanjs/base";
5406
+ ${content}`;
5407
+ };
5408
+ var insertEnumClass = (content, enumClassName, enumName, values) => {
5409
+ if (content.includes(`export class ${enumClassName} extends enumOf`))
5410
+ return content;
5411
+ const enumClass = `export class ${enumClassName} extends enumOf("${enumName}", [
5412
+ ${values.map((value) => ` ${JSON.stringify(value)},`).join(`
5413
+ `)}
5414
+ ] as const) {}
5415
+
5416
+ `;
5417
+ const firstClassIndex = content.indexOf("export class ");
5418
+ if (firstClassIndex < 0)
5419
+ return `${content}
5420
+ ${enumClass}`;
5421
+ return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
5422
+ };
5423
+ var insertDictionaryModelField = (content, moduleClassName, fieldName) => {
5424
+ if (new RegExp(`\\b${fieldName}\\s*:`).test(content))
5425
+ return content;
5426
+ const label = bilingualLabelForField(fieldName);
5427
+ const desc = bilingualDescriptionForField(fieldName);
5428
+ const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
5429
+ if (modelIndex < 0)
5430
+ return null;
5431
+ const objectEndIndex = content.indexOf(" }))", modelIndex);
5432
+ if (objectEndIndex < 0)
5433
+ return null;
5434
+ return `${content.slice(0, objectEndIndex)} ${fieldName}: t([${JSON.stringify(label.en)}, ${JSON.stringify(label.ko)}]).desc([${JSON.stringify(desc.en)}, ${JSON.stringify(desc.ko)}]),
5435
+ ${content.slice(objectEndIndex)}`;
5436
+ };
5437
+ var ensureConstantTypeImport = (content, constantPath, typeName) => {
5438
+ if (new RegExp(`import type \\{[^}]*\\b${typeName}\\b[^}]*\\} from "${constantPath}";`).test(content))
5439
+ return content;
5440
+ const importPattern = new RegExp(`import type \\{ ([^}]+) \\} from "${constantPath}";`);
5441
+ const existingImport = content.match(importPattern);
5442
+ if (existingImport !== null) {
5443
+ const names = existingImport[1]?.split(",").map((name) => name.trim()) ?? [];
5444
+ return content.replace(existingImport[0], `import type { ${[...names, typeName].sort().join(", ")} } from "${constantPath}";`);
5445
+ }
5446
+ return `import type { ${typeName} } from "${constantPath}";
5447
+ ${content}`;
5448
+ };
5449
+ var insertDictionaryEnum = (content, enumClassName, enumName, values) => {
5450
+ if (content.includes(`.enum<${enumClassName}>("${enumName}"`))
5451
+ return content;
5452
+ const enumBlock = ` .enum<${enumClassName}>("${enumName}", (t) => ({
5453
+ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${JSON.stringify(titleize(value))}]),`).join(`
5454
+ `)}
5455
+ }))
5456
+ `;
5457
+ const chainEndIndex = content.lastIndexOf(";");
5458
+ const insertBeforeIndex = [content.indexOf(".error("), content.indexOf(".translate("), chainEndIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
5459
+ if (insertBeforeIndex === undefined)
5460
+ return null;
5461
+ return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
5462
+ };
5463
+ var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
5464
+
5465
+ // pkgs/@akanjs/devkit/workflow/uiPolicy.ts
5466
+ var addFieldUiPolicyForType = (typeName) => {
5467
+ const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
5468
+ if (normalizedType === "Int" || normalizedType === "Float") {
5469
+ return {
5470
+ normalizedType,
5471
+ component: "Field.Number",
5472
+ confidence: "high",
5473
+ autoTemplateSupported: true
5474
+ };
5475
+ }
5476
+ if (normalizedType === "Date") {
5477
+ return {
5478
+ normalizedType,
5479
+ component: "Field.Date",
5480
+ confidence: "high",
5481
+ autoTemplateSupported: true
5482
+ };
5483
+ }
5484
+ if (normalizedType === "Boolean" || normalizedType === "enum") {
5485
+ return {
5486
+ normalizedType,
5487
+ component: "Field.ToggleSelect",
5488
+ confidence: "medium",
5489
+ autoTemplateSupported: false
5490
+ };
5491
+ }
5492
+ if (normalizedType === "String") {
5493
+ return {
5494
+ normalizedType,
5495
+ component: "Field.Text",
5496
+ confidence: "high",
5497
+ autoTemplateSupported: true
5498
+ };
5499
+ }
5500
+ return {
5501
+ normalizedType,
5502
+ component: "Field.Text",
5503
+ confidence: "low",
5504
+ autoTemplateSupported: false
5505
+ };
5506
+ };
5507
+
5508
+ // pkgs/@akanjs/devkit/workflow/executor.ts
5509
+ var workflowStepKey = (workflow, stepId) => `${workflow}:${stepId}`;
5510
+ var primitiveReportToWorkflowStepResult = (report) => ({
5511
+ changedFiles: report.changedFiles,
5512
+ generatedFiles: report.generatedFiles,
5513
+ commands: report.validationCommands,
5514
+ diagnostics: report.diagnostics,
5515
+ recommendations: [],
5516
+ nextActions: report.nextActions
5517
+ });
5518
+ var workflowStringInput = (value) => typeof value === "string" ? value : null;
5519
+ var workflowStringListInput = (value) => Array.isArray(value) ? value.join(",") : null;
5520
+ var workflowStringArrayInput = (value) => Array.isArray(value) ? value : null;
5521
+ var workflowBooleanInput = (value) => typeof value === "boolean" ? value : null;
5522
+ var resolveWorkflowSys = async (workspace, target) => {
5523
+ if (!target)
5524
+ return null;
5525
+ const [apps, libs] = await workspace.getSyss();
5526
+ if (apps.includes(target))
5527
+ return AppExecutor.from(workspace, target);
5528
+ if (libs.includes(target))
5529
+ return LibExecutor.from(workspace, target);
5202
5530
  return null;
5203
5531
  };
5204
5532
  var targetMissing = (input2 = "app") => ({
@@ -5220,19 +5548,24 @@ var unsupportedInput = (input2, message) => ({
5220
5548
  message
5221
5549
  });
5222
5550
  var addFieldUiSurfaceInspection = (plan) => {
5551
+ const app = workflowStringInput(plan.inputs.app);
5223
5552
  const module = workflowStringInput(plan.inputs.module) ?? "<module>";
5224
5553
  const field = workflowStringInput(plan.inputs.field) ?? "<field>";
5554
+ const typeName = workflowStringInput(plan.inputs.type);
5555
+ const policy = addFieldUiPolicyForType(typeName ?? "String");
5556
+ const surfaces = workflowStringArrayInput(plan.inputs.surfaces);
5557
+ const templateRequested = surfaces?.includes("template") ?? false;
5225
5558
  const moduleClassName = capitalize2(module);
5226
- const target = `*/lib/${module}/${moduleClassName}.Template.tsx`;
5559
+ const target = `${app ? `apps/${app}` : "*"}/${moduleSourcePaths(module).template}`;
5227
5560
  return {
5228
5561
  recommendations: [
5229
5562
  {
5230
5563
  code: "add-field-ui-surface-review",
5231
5564
  kind: "manual-action",
5232
5565
  target,
5233
- action: `Add ${field} to ${moduleClassName} UI surfaces only when the local Field.* pattern is clear.`,
5566
+ action: templateRequested ? `Template was requested for ${field}. If no Template file changed, auto-edit was skipped because the file was missing, the generated ${module}Form/Layout.Template pattern was not found, or ${policy.component} needs option binding. Candidate position: inside Layout.Template near the existing Field components.` : `Template was not selected, so UI files are intentionally left unchanged. Candidate positions if you expose it later: Layout.Template field list for editing, Light${moduleClassName} projection for list/card data, and Unit/View card sections for display.`,
5234
5567
  confidence: "medium",
5235
- message: `Review Template/Unit/View/Store surfaces for ${module}.${field}; no UI file was modified automatically.`
5568
+ message: `Review UI surfaces for ${module}.${field}; recommended component is ${policy.component}, with manual reasons and candidate positions in action.`
5236
5569
  }
5237
5570
  ],
5238
5571
  nextActions: [
@@ -5300,7 +5633,9 @@ var createWorkflowStepRegistry = ({
5300
5633
  module: workflowStringInput(plan.inputs.module),
5301
5634
  field: workflowStringInput(plan.inputs.field),
5302
5635
  values: workflowStringListInput(plan.inputs.values),
5303
- defaultValue: workflowStringInput(plan.inputs.default)
5636
+ defaultValue: workflowStringInput(plan.inputs.default),
5637
+ surfaces: workflowStringArrayInput(plan.inputs.surfaces),
5638
+ includeInLight: workflowBooleanInput(plan.inputs.includeInLight)
5304
5639
  }));
5305
5640
  }
5306
5641
  return primitiveReportToWorkflowStepResult(await addField({
@@ -5308,7 +5643,9 @@ var createWorkflowStepRegistry = ({
5308
5643
  module: workflowStringInput(plan.inputs.module),
5309
5644
  field: workflowStringInput(plan.inputs.field),
5310
5645
  type: workflowStringInput(plan.inputs.type),
5311
- defaultValue: workflowStringInput(plan.inputs.default)
5646
+ defaultValue: workflowStringInput(plan.inputs.default),
5647
+ surfaces: workflowStringArrayInput(plan.inputs.surfaces),
5648
+ includeInLight: workflowBooleanInput(plan.inputs.includeInLight)
5312
5649
  }));
5313
5650
  },
5314
5651
  [workflowStepKey("add-field", "update-dictionary")]: inspect,
@@ -5324,209 +5661,548 @@ var createWorkflowStepRegistry = ({
5324
5661
  [workflowStepKey("add-enum-field", "update-option")]: inspect
5325
5662
  };
5326
5663
  };
5327
- var renderWorkflowApplyReport = (report) => [
5328
- `# Workflow Apply: ${report.workflow}`,
5329
- "",
5330
- `- Mode: ${report.mode}`,
5331
- `- Status: ${report.status}`,
5664
+ class WorkflowExecutor {
5665
+ registry;
5666
+ constructor(registry) {
5667
+ this.registry = registry;
5668
+ }
5669
+ async apply(plan) {
5670
+ const changedFiles = [];
5671
+ const generatedFiles = [];
5672
+ const recommendedValidationCommands = [];
5673
+ const diagnostics = [...plan.diagnostics];
5674
+ const recommendations = [...plan.recommendations];
5675
+ const nextActions = [];
5676
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
5677
+ return createWorkflowApplyReport({
5678
+ workflow: plan.workflow,
5679
+ mode: "apply",
5680
+ changedFiles,
5681
+ generatedFiles,
5682
+ recommendedValidationCommands,
5683
+ diagnostics,
5684
+ recommendations,
5685
+ nextActions,
5686
+ plan
5687
+ });
5688
+ }
5689
+ recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
5690
+ nextActions.push(...workflowCommandsForPlan(plan));
5691
+ for (const step of plan.steps) {
5692
+ const runner = this.registry[workflowStepKey(plan.workflow, step.id)] ?? this.registry[step.tool] ?? this.registry[step.id];
5693
+ if (!runner) {
5694
+ diagnostics.push({
5695
+ severity: "error",
5696
+ code: "workflow-step-unsupported",
5697
+ message: `Workflow ${plan.workflow} step "${step.id}" is not supported by workflow apply yet.`
5698
+ });
5699
+ nextActions.push({
5700
+ command: `akan workflow explain ${plan.workflow}`,
5701
+ reason: "Review the unsupported workflow step before retrying apply."
5702
+ });
5703
+ break;
5704
+ }
5705
+ const result = await runner(step, plan);
5706
+ if (!result)
5707
+ continue;
5708
+ changedFiles.push(...result.changedFiles ?? []);
5709
+ generatedFiles.push(...result.generatedFiles ?? []);
5710
+ recommendedValidationCommands.push(...result.commands ?? []);
5711
+ diagnostics.push(...result.diagnostics ?? []);
5712
+ recommendations.push(...result.recommendations ?? []);
5713
+ nextActions.push(...result.nextActions ?? []);
5714
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
5715
+ break;
5716
+ }
5717
+ return createWorkflowApplyReport({
5718
+ workflow: plan.workflow,
5719
+ mode: "apply",
5720
+ changedFiles,
5721
+ generatedFiles,
5722
+ recommendedValidationCommands,
5723
+ diagnostics,
5724
+ recommendations,
5725
+ nextActions,
5726
+ plan
5727
+ });
5728
+ }
5729
+ }
5730
+ // pkgs/@akanjs/devkit/workflow/plan.ts
5731
+ import { capitalize as capitalize3 } from "akanjs/common";
5732
+ var surfaceModes = new Set(["infer", "include", "skip"]);
5733
+ var parseStringList = (value) => {
5734
+ if (Array.isArray(value)) {
5735
+ const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
5736
+ return values.length === value.length ? values : null;
5737
+ }
5738
+ if (typeof value !== "string")
5739
+ return null;
5740
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
5741
+ };
5742
+ var normalizeInputValue = (name, spec, value) => {
5743
+ if (spec.type === "string")
5744
+ return typeof value === "string" && value.length > 0 ? value : null;
5745
+ if (spec.type === "string-list") {
5746
+ const values = parseStringList(value);
5747
+ return values && values.length > 0 ? values : null;
5748
+ }
5749
+ if (spec.type === "boolean") {
5750
+ if (typeof value === "boolean")
5751
+ return value;
5752
+ if (typeof value !== "string")
5753
+ return null;
5754
+ const lowered = value.trim().toLowerCase();
5755
+ if (lowered === "true")
5756
+ return true;
5757
+ if (lowered === "false")
5758
+ return false;
5759
+ return null;
5760
+ }
5761
+ if (typeof value === "string" && surfaceModes.has(value))
5762
+ return value;
5763
+ throw new Error(`Unsupported workflow input value for ${name}`);
5764
+ };
5765
+ var listWorkflowSpecs = (specs) => [...specs].sort((a, b) => a.name.localeCompare(b.name));
5766
+ var getWorkflowSpec = (specs, name) => specs.find((spec) => spec.name === name) ?? null;
5767
+ var compactWorkflowInputs = (inputs) => Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
5768
+ var addFieldTargetRoot = (app) => app ? `apps/${app}` : "*";
5769
+ var addFieldPaths = (inputs) => {
5770
+ const app = typeof inputs.app === "string" ? inputs.app : null;
5771
+ const module = typeof inputs.module === "string" ? inputs.module : "<module>";
5772
+ const paths = moduleSourcePaths(module);
5773
+ const root = addFieldTargetRoot(app);
5774
+ return {
5775
+ constant: `${root}/${paths.constant}`,
5776
+ dictionary: `${root}/${paths.dictionary}`,
5777
+ template: `${root}/${paths.template}`,
5778
+ unit: `${root}/${paths.unit}`,
5779
+ view: `${root}/${paths.view}`
5780
+ };
5781
+ };
5782
+ var selectedAddFieldSurfaces = (inputs) => Array.isArray(inputs.surfaces) ? new Set(inputs.surfaces) : null;
5783
+ var addFieldDefaultCoercion = (inputs) => {
5784
+ const typeName = typeof inputs.type === "string" ? inputs.type : null;
5785
+ const defaultValue = typeof inputs.default === "string" ? inputs.default : null;
5786
+ if (!typeName || defaultValue === null)
5787
+ return null;
5788
+ if (typeName.toLowerCase() === "number" || typeName.toLowerCase() === "numeric")
5789
+ return null;
5790
+ const enumValues = Array.isArray(inputs.values) ? inputs.values : null;
5791
+ return coerceFieldDefault(typeName.toLowerCase() === "enum" ? "enum" : typeName, defaultValue, { enumValues });
5792
+ };
5793
+ var createAddFieldDefaultRecommendations = (inputs) => {
5794
+ const field = typeof inputs.field === "string" ? inputs.field : "<field>";
5795
+ const coercion = addFieldDefaultCoercion(inputs);
5796
+ if (!coercion?.normalized || !coercion.expression)
5797
+ return [];
5798
+ return [
5799
+ {
5800
+ code: "add-field-default-normalized",
5801
+ kind: "manual-action",
5802
+ confidence: "high",
5803
+ message: `Default for ${field} will be normalized to ${coercion.normalizedType} literal ${coercion.expression}.`,
5804
+ action: "Review the normalized default in the apply report if this value should stay unset."
5805
+ }
5806
+ ];
5807
+ };
5808
+ var createAddFieldRecommendations = (inputs) => {
5809
+ const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
5810
+ const module = typeof inputs.module === "string" ? inputs.module : "<module>";
5811
+ const field = typeof inputs.field === "string" ? inputs.field : "<field>";
5812
+ const typeName = typeof inputs.type === "string" ? inputs.type : null;
5813
+ if (!typeName)
5814
+ return [];
5815
+ const policy = addFieldUiPolicyForType(typeName);
5816
+ const normalizedType = policy.normalizedType;
5817
+ const paths = addFieldPaths(inputs);
5818
+ const surfaces = selectedAddFieldSurfaces(inputs);
5819
+ const templateRequested = surfaces?.has("template") ?? false;
5820
+ const includeInLight = inputs.includeInLight === true;
5821
+ return [
5822
+ ...normalizedType === "Int" || normalizedType === "Float" ? [
5823
+ {
5824
+ code: "add-field-import",
5825
+ kind: "import",
5826
+ target: paths.constant,
5827
+ confidence: "high",
5828
+ message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`
5829
+ }
5830
+ ] : [],
5831
+ {
5832
+ code: "add-field-placement-constant",
5833
+ kind: "placement",
5834
+ target: paths.constant,
5835
+ confidence: "high",
5836
+ message: `Insert ${field}: field(${normalizedType}) in ${capitalize3(module)}Input.`
5837
+ },
5838
+ {
5839
+ code: "add-field-placement-dictionary",
5840
+ kind: "placement",
5841
+ target: paths.dictionary,
5842
+ confidence: "high",
5843
+ message: `Add dictionary labels for ${module}.${field}.`
5844
+ },
5845
+ {
5846
+ code: "add-field-component",
5847
+ kind: "ui-component",
5848
+ target: paths.template,
5849
+ confidence: policy.confidence,
5850
+ action: templateRequested ? `apply_workflow will try to add ${policy.component} to Template when the existing Template has a safe generated field-list pattern.` : `Recommended component is ${policy.component}. Pass surfaces=["template"] when this field should be rendered in the Template form.`,
5851
+ message: `Recommended UI component for ${field} (${normalizedType}): ${policy.component}.`
5852
+ },
5853
+ ...!surfaces ? [
5854
+ {
5855
+ code: "add-field-template-surface-choice",
5856
+ kind: "manual-action",
5857
+ target: paths.template,
5858
+ action: `Choose whether to expose ${field} in Template by passing surfaces=["template"].`,
5859
+ confidence: "medium",
5860
+ message: `Template exposure for ${module}.${field} is not selected yet.`
5861
+ }
5862
+ ] : [],
5863
+ ...!includeInLight ? [
5864
+ {
5865
+ code: "add-field-light-projection-choice",
5866
+ kind: "manual-action",
5867
+ target: paths.constant,
5868
+ action: `Pass includeInLight=true when ${field} should appear in Light${capitalize3(module)} list/card projections.`,
5869
+ confidence: "medium",
5870
+ message: `Light projection exposure for ${module}.${field} is not selected yet.`
5871
+ }
5872
+ ] : [],
5873
+ ...includeInLight ? [
5874
+ {
5875
+ code: "add-field-light-projection",
5876
+ kind: "placement",
5877
+ target: paths.constant,
5878
+ confidence: "high",
5879
+ message: `Add ${field} to Light${capitalize3(module)} projection fields.`
5880
+ }
5881
+ ] : [],
5882
+ ...surfaces && !templateRequested ? [
5883
+ {
5884
+ code: "add-field-ui-surface-skip",
5885
+ kind: "manual-action",
5886
+ target: paths.template,
5887
+ confidence: "medium",
5888
+ message: `Template is not included in surfaces, so ${module}.${field} will not be auto-rendered there.`
5889
+ }
5890
+ ] : [],
5891
+ {
5892
+ code: "add-field-ui-manual-review",
5893
+ kind: "manual-action",
5894
+ target: paths.template,
5895
+ action: `Review ${app}:${module} UI after apply. Template auto-edit requires a generated ${module}Form hook and Layout.Template field list; Unit/View are not auto-edited because list/card placement depends on local layout. Candidate positions: Layout.Template before the closing tag, Light${capitalize3(module)} projection array for list data, and Unit/View card sections for display.`,
5896
+ confidence: "medium",
5897
+ message: "UI manual review includes the reason auto-edit may be skipped and candidate insertion positions."
5898
+ },
5899
+ ...createAddFieldDefaultRecommendations(inputs)
5900
+ ];
5901
+ };
5902
+ var createWorkflowPlanPredictedChanges = (spec, inputs) => {
5903
+ if (spec.name !== "add-field")
5904
+ return spec.predictedChanges;
5905
+ const paths = addFieldPaths(inputs);
5906
+ const surfaces = selectedAddFieldSurfaces(inputs);
5907
+ const includeInLight = inputs.includeInLight === true;
5908
+ return [
5909
+ {
5910
+ target: paths.constant,
5911
+ action: "modify",
5912
+ applyScope: "auto",
5913
+ reason: includeInLight ? "Field shape and Light projection are added." : "Field shape is added."
5914
+ },
5915
+ {
5916
+ target: paths.dictionary,
5917
+ action: "modify",
5918
+ applyScope: "auto",
5919
+ reason: "Field label is added."
5920
+ },
5921
+ ...!surfaces || surfaces.has("template") ? [
5922
+ {
5923
+ target: paths.template,
5924
+ action: "modify",
5925
+ applyScope: surfaces?.has("template") ? "auto" : "manual-review",
5926
+ reason: surfaces?.has("template") ? "Template form is selected for safe-pattern auto insertion." : "Form surface may include the field."
5927
+ }
5928
+ ] : [],
5929
+ {
5930
+ target: `${addFieldTargetRoot(typeof inputs.app === "string" ? inputs.app : null)}/lib/cnst.ts`,
5931
+ action: "sync",
5932
+ applyScope: "generated-sync",
5933
+ reason: "Generated constants may change after sync."
5934
+ },
5935
+ {
5936
+ target: `${addFieldTargetRoot(typeof inputs.app === "string" ? inputs.app : null)}/lib/dict.ts`,
5937
+ action: "sync",
5938
+ applyScope: "generated-sync",
5939
+ reason: "Generated dictionary barrel may change after sync."
5940
+ }
5941
+ ];
5942
+ };
5943
+ var createWorkflowPlanOptionalSurfaces = (spec, inputs) => {
5944
+ const optionalSurfaces = spec.optionalSurfaces ?? {};
5945
+ const surfaces = selectedAddFieldSurfaces(inputs);
5946
+ if (spec.name !== "add-field" || !surfaces)
5947
+ return optionalSurfaces;
5948
+ return Object.fromEntries(Object.entries(optionalSurfaces).map(([surface, mode]) => [surface, surfaces.has(surface) ? "include" : mode]));
5949
+ };
5950
+ var createWorkflowPlanRecommendations = (spec, inputs) => [
5951
+ {
5952
+ code: "workflow-apply-first",
5953
+ kind: "auto-apply",
5954
+ confidence: "high",
5955
+ action: "Call apply_workflow with the MCP planPath before editing source files directly.",
5956
+ message: `Apply the ${spec.name} plan through apply_workflow when MCP returns planPath or next.tool=apply_workflow.`
5957
+ },
5958
+ {
5959
+ code: "workflow-validate-apply-report",
5960
+ kind: "validation",
5961
+ confidence: "high",
5962
+ action: "After apply_workflow, call run_validation with validationTarget when present; otherwise use applyReportPath.",
5963
+ message: "Validate the apply report artifact so diagnostics and recommendations follow the actual apply result."
5964
+ },
5965
+ ...spec.name === "add-field" ? createAddFieldRecommendations(inputs) : []
5966
+ ];
5967
+ var createWorkflowPlan = (spec, rawInputs) => {
5968
+ const inputs = {};
5969
+ const diagnostics = [];
5970
+ for (const [name, inputSpec] of Object.entries(spec.inputs)) {
5971
+ const rawValue = rawInputs[name];
5972
+ if (rawValue === undefined || rawValue === null || rawValue === "") {
5973
+ if (inputSpec.required) {
5974
+ diagnostics.push({
5975
+ severity: "error",
5976
+ code: "workflow-input-missing",
5977
+ input: name,
5978
+ message: `Workflow ${spec.name} requires input "${name}".`
5979
+ });
5980
+ }
5981
+ continue;
5982
+ }
5983
+ const value = normalizeInputValue(name, inputSpec, rawValue);
5984
+ if (value === null) {
5985
+ diagnostics.push({
5986
+ severity: "error",
5987
+ code: "workflow-input-invalid",
5988
+ input: name,
5989
+ message: `Workflow ${spec.name} input "${name}" must be ${inputSpec.type}.`
5990
+ });
5991
+ continue;
5992
+ }
5993
+ if (inputSpec.allowedValues && typeof value === "string" && !inputSpec.allowedValues.includes(value)) {
5994
+ diagnostics.push({
5995
+ severity: "error",
5996
+ code: "workflow-input-not-allowed",
5997
+ input: name,
5998
+ message: `Workflow ${spec.name} input "${name}" must be one of: ${inputSpec.allowedValues.join(", ")}.`
5999
+ });
6000
+ continue;
6001
+ }
6002
+ inputs[name] = value;
6003
+ }
6004
+ const fieldType = inputs.type;
6005
+ if (spec.name === "add-field" && typeof fieldType === "string" && (fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")) {
6006
+ diagnostics.push({
6007
+ severity: "error",
6008
+ code: "primitive-field-type-unsupported",
6009
+ input: "type",
6010
+ message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`
6011
+ });
6012
+ }
6013
+ if (spec.name === "add-field") {
6014
+ const defaultCoercion = addFieldDefaultCoercion(inputs);
6015
+ if (defaultCoercion?.diagnostic) {
6016
+ diagnostics.push({
6017
+ ...defaultCoercion.diagnostic,
6018
+ code: "workflow-default-value-invalid",
6019
+ message: `Plan input default is not valid for ${defaultCoercion.normalizedType}: ${defaultCoercion.diagnostic.message}`
6020
+ });
6021
+ } else if (defaultCoercion?.normalized && defaultCoercion.expression) {
6022
+ diagnostics.push({
6023
+ severity: "warning",
6024
+ code: "workflow-default-value-normalized",
6025
+ input: "default",
6026
+ failureScope: "source-change",
6027
+ message: `Plan input default will be written as ${defaultCoercion.normalizedType} literal ${defaultCoercion.expression}.`
6028
+ });
6029
+ }
6030
+ }
6031
+ return {
6032
+ schemaVersion: 1,
6033
+ workflow: spec.name,
6034
+ mode: "plan",
6035
+ inputs,
6036
+ optionalSurfaces: createWorkflowPlanOptionalSurfaces(spec, inputs),
6037
+ steps: spec.steps,
6038
+ predictedChanges: createWorkflowPlanPredictedChanges(spec, inputs),
6039
+ validation: spec.validation,
6040
+ diagnostics,
6041
+ recommendations: createWorkflowPlanRecommendations(spec, inputs),
6042
+ requiresApproval: true
6043
+ };
6044
+ };
6045
+ // pkgs/@akanjs/devkit/workflow/render.ts
6046
+ var renderWorkflowList = (specs) => [
6047
+ "# Akan Workflows",
5332
6048
  "",
5333
- "## Changed Files",
5334
- ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
6049
+ ...specs.flatMap((spec) => [`- \`${spec.name}\`: ${spec.description}`, ` - When: ${spec.whenToUse}`]),
6050
+ ""
6051
+ ].join(`
6052
+ `);
6053
+ var renderWorkflowExplain = (spec) => [
6054
+ `# Workflow: ${spec.name}`,
5335
6055
  "",
5336
- "## Generated Files",
5337
- ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
6056
+ spec.description,
5338
6057
  "",
5339
- "## Applied Commands",
5340
- ...report.appliedCommands.length ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
6058
+ "## When To Use",
6059
+ spec.whenToUse,
5341
6060
  "",
5342
- "## Recommended Validation Commands",
5343
- ...report.recommendedValidationCommands.length ? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
6061
+ "## Inputs",
6062
+ ...Object.entries(spec.inputs).map(([name, input2]) => `- \`${name}\`${input2.required ? " (required)" : ""}: ${input2.description}${input2.allowedValues ? ` Allowed: ${input2.allowedValues.join(", ")}.` : ""}`),
5344
6063
  "",
5345
- "## Diagnostics",
5346
- ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
6064
+ "## Optional Surfaces",
6065
+ ...Object.entries(spec.optionalSurfaces ?? {}).map(([name, mode]) => `- \`${name}\`: ${mode}`),
5347
6066
  "",
5348
- "## Recommendations",
5349
- ...report.recommendations.length ? report.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`) : ["- none"],
6067
+ "## Steps",
6068
+ ...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
5350
6069
  "",
5351
- "## Next Actions",
5352
- ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
6070
+ "## Validation",
6071
+ ...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
5353
6072
  ""
5354
6073
  ].join(`
5355
6074
  `);
5356
- var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
5357
- var renderWorkflowValidationRunReport = (report) => [
5358
- `# Workflow Validation: ${report.workflow}`,
6075
+ var renderWorkflowPlan = (plan) => [
6076
+ `# Workflow Plan: ${plan.workflow}`,
5359
6077
  "",
5360
- `- Run: ${report.runId}`,
5361
- `- Status: ${report.status}`,
6078
+ `- Mode: ${plan.mode}`,
6079
+ `- Requires approval: ${plan.requiresApproval}`,
5362
6080
  "",
5363
- "## Commands",
5364
- ...report.commands.length ? report.commands.map((command) => {
5365
- const scope = command.failureScope ? ` (${command.failureScope})` : "";
5366
- return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
5367
- }) : ["- none"],
6081
+ "## Inputs",
6082
+ ...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
5368
6083
  "",
5369
- "## Diagnostics",
5370
- ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
6084
+ "## Optional Surfaces",
6085
+ ...Object.entries(plan.optionalSurfaces).map(([name, mode]) => `- \`${name}\`: ${mode}`),
5371
6086
  "",
5372
- "## Repair Actions",
5373
- ...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
6087
+ "## Steps",
6088
+ ...plan.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
5374
6089
  "",
5375
- "## Next Actions",
5376
- ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
6090
+ "## Predicted Changes",
6091
+ ...plan.predictedChanges.map((change) => {
6092
+ const scope = change.applyScope ? ` (${change.applyScope})` : "";
6093
+ return `- \`${change.action}\`${scope} ${change.target}: ${change.reason}`;
6094
+ }),
6095
+ "",
6096
+ "## Validation",
6097
+ ...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
6098
+ "",
6099
+ "## Diagnostics",
6100
+ ...plan.diagnostics.length ? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
6101
+ "",
6102
+ "## Recommendations",
6103
+ ...plan.recommendations.length ? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`) : ["- none"],
5377
6104
  ""
5378
6105
  ].join(`
5379
6106
  `);
5380
- var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
5381
- var renderWorkflowRunArtifact = (artifact, format = "markdown") => {
5382
- if ("kind" in artifact)
5383
- return renderRepairReport(artifact, format);
5384
- if ("mode" in artifact && artifact.mode === "validate")
5385
- return renderWorkflowValidation(artifact, format);
5386
- if ("mode" in artifact && (artifact.mode === "apply" || artifact.mode === "dry-run")) {
5387
- return renderWorkflowApply(artifact, format);
5388
- }
5389
- return jsonText(artifact);
6107
+ var renderRecommendation = (recommendation) => {
6108
+ const target = recommendation.target ? ` ${recommendation.target}` : "";
6109
+ const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
6110
+ return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
5390
6111
  };
5391
- var createRepairReport = ({
5392
- command,
5393
- kind,
5394
- target = null,
5395
- diagnostics = [],
5396
- repairActions = [],
5397
- nextActions = [],
5398
- commands = [],
5399
- generatedFiles = [],
5400
- syncedAt
5401
- }) => ({
5402
- schemaVersion: 1,
5403
- command,
5404
- kind,
5405
- target,
5406
- status: workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed",
5407
- diagnostics,
5408
- repairActions: uniqueBy(repairActions, (action) => action.command),
5409
- nextActions: uniqueBy(nextActions, (action) => action.command),
5410
- commands,
5411
- generatedFiles,
5412
- ...syncedAt ? { syncedAt } : {}
5413
- });
5414
- var renderRepairReportMarkdown = (report) => [
5415
- `# Akan Repair: ${report.kind}`,
6112
+ var renderDiagnostic = (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
6113
+ var renderWorkflowApplyReport = (report) => {
6114
+ const manualReviewItems = [
6115
+ ...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
6116
+ ...report.recommendations.filter((recommendation) => recommendation.kind === "manual-action").map(renderRecommendation)
6117
+ ];
6118
+ const validationBlockers = report.diagnostics.filter((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "workspace-config" || diagnostic.failureScope === "environment")).map(renderDiagnostic);
6119
+ return [
6120
+ `# Workflow Apply: ${report.workflow}`,
6121
+ "",
6122
+ `- Mode: ${report.mode}`,
6123
+ `- Status: ${report.status}`,
6124
+ ...report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : [],
6125
+ "",
6126
+ "## Automatically Modified",
6127
+ ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
6128
+ "",
6129
+ "## Generated Sync",
6130
+ ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
6131
+ "",
6132
+ "## Applied Commands",
6133
+ ...report.appliedCommands.length ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
6134
+ "",
6135
+ "## Recommended Validation Commands",
6136
+ ...report.recommendedValidationCommands.length ? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
6137
+ "",
6138
+ "## User Review Required",
6139
+ ...manualReviewItems.length ? manualReviewItems : ["- none"],
6140
+ "",
6141
+ "## Validation Blockers",
6142
+ ...validationBlockers.length ? validationBlockers : report.recommendedValidationCommands.length ? ["- none detected during apply; run validation with the validation target for command-level blockers."] : ["- none"],
6143
+ "",
6144
+ "## Diagnostics",
6145
+ ...report.diagnostics.length ? report.diagnostics.map(renderDiagnostic) : ["- none"],
6146
+ "",
6147
+ "## Recommendations",
6148
+ ...report.recommendations.length ? report.recommendations.map(renderRecommendation) : ["- none"],
6149
+ "",
6150
+ "## Next Actions",
6151
+ ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
6152
+ ""
6153
+ ].join(`
6154
+ `);
6155
+ };
6156
+ var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
6157
+ var renderWorkflowValidationRunReport = (report) => [
6158
+ `# Workflow Validation: ${report.workflow}`,
5416
6159
  "",
6160
+ `- Run: ${report.runId}`,
5417
6161
  `- Status: ${report.status}`,
5418
- `- Target: ${report.target ?? "none"}`,
6162
+ `- Source status: ${report.sourceStatus}`,
6163
+ `- Workspace status: ${report.workspaceStatus}`,
6164
+ `- Overall status: ${report.overallStatus}`,
5419
6165
  "",
5420
- "## Commands",
5421
- ...report.commands.length ? report.commands.map((command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`) : ["- none"],
6166
+ "## Status Summary",
6167
+ `- Required source-change validation: ${report.summary.sourceChange}`,
6168
+ `- Generated sync validation: ${report.summary.generatedSync}`,
6169
+ `- Workspace configuration validation: ${report.summary.workspaceConfig}`,
6170
+ `- Environment validation: ${report.summary.environment}`,
5422
6171
  "",
5423
- "## Diagnostics",
5424
- ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
6172
+ "## Known Blockers",
6173
+ ...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
6174
+ const command = blocker.command ? ` \`${blocker.command}\`` : "";
6175
+ const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
6176
+ const known = blocker.known ? " known" : "";
6177
+ return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
6178
+ }) : ["- none"],
5425
6179
  "",
5426
- "## Next Actions",
5427
- ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
5428
- ""
5429
- ].join(`
5430
- `);
5431
- var renderRepairReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderRepairReportMarkdown(report);
5432
-
5433
- class WorkflowExecutor {
5434
- registry;
5435
- constructor(registry) {
5436
- this.registry = registry;
5437
- }
5438
- async apply(plan) {
5439
- const changedFiles = [];
5440
- const generatedFiles = [];
5441
- const recommendedValidationCommands = [];
5442
- const diagnostics = [...plan.diagnostics];
5443
- const recommendations = [...plan.recommendations];
5444
- const nextActions = [];
5445
- if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
5446
- return createWorkflowApplyReport({
5447
- workflow: plan.workflow,
5448
- mode: "apply",
5449
- changedFiles,
5450
- generatedFiles,
5451
- recommendedValidationCommands,
5452
- diagnostics,
5453
- recommendations,
5454
- nextActions,
5455
- plan
5456
- });
5457
- }
5458
- recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
5459
- nextActions.push(...workflowCommandsForPlan(plan));
5460
- for (const step of plan.steps) {
5461
- const runner = this.registry[workflowStepKey(plan.workflow, step.id)] ?? this.registry[step.tool] ?? this.registry[step.id];
5462
- if (!runner) {
5463
- diagnostics.push({
5464
- severity: "error",
5465
- code: "workflow-step-unsupported",
5466
- message: `Workflow ${plan.workflow} step "${step.id}" is not supported by workflow apply yet.`
5467
- });
5468
- nextActions.push({
5469
- command: `akan workflow explain ${plan.workflow}`,
5470
- reason: "Review the unsupported workflow step before retrying apply."
5471
- });
5472
- break;
5473
- }
5474
- const result = await runner(step, plan);
5475
- if (!result)
5476
- continue;
5477
- changedFiles.push(...result.changedFiles ?? []);
5478
- generatedFiles.push(...result.generatedFiles ?? []);
5479
- recommendedValidationCommands.push(...result.commands ?? []);
5480
- diagnostics.push(...result.diagnostics ?? []);
5481
- recommendations.push(...result.recommendations ?? []);
5482
- nextActions.push(...result.nextActions ?? []);
5483
- if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
5484
- break;
5485
- }
5486
- return createWorkflowApplyReport({
5487
- workflow: plan.workflow,
5488
- mode: "apply",
5489
- changedFiles,
5490
- generatedFiles,
5491
- recommendedValidationCommands,
5492
- diagnostics,
5493
- recommendations,
5494
- nextActions,
5495
- plan
5496
- });
5497
- }
5498
- }
5499
- var createPrimitiveWriteReport = ({
5500
- command,
5501
- status,
5502
- changedFiles = [],
5503
- generatedFiles = [],
5504
- validationCommands = [],
5505
- diagnostics = [],
5506
- nextActions = []
5507
- }) => ({
5508
- schemaVersion: 1,
5509
- command,
5510
- status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
5511
- changedFiles,
5512
- generatedFiles,
5513
- validationCommands,
5514
- diagnostics,
5515
- nextActions
5516
- });
5517
- var renderPrimitiveWriteReport = (report) => [
5518
- `# Primitive Write: ${report.command}`,
6180
+ "## Commands",
6181
+ ...report.commands.length ? report.commands.map((command) => {
6182
+ const scope = command.failureScope ? ` (${command.failureScope})` : "";
6183
+ return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
6184
+ }) : ["- none"],
5519
6185
  "",
5520
- `- Status: ${report.status}`,
6186
+ "## Diagnostics",
6187
+ ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
5521
6188
  "",
5522
- "## Changed Files",
5523
- ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
6189
+ "## Repair Actions",
6190
+ ...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
5524
6191
  "",
5525
- "## Generated Files",
5526
- ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
6192
+ "## Next Actions",
6193
+ ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
6194
+ ""
6195
+ ].join(`
6196
+ `);
6197
+ var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
6198
+ var renderRepairReportMarkdown = (report) => [
6199
+ `# Akan Repair: ${report.kind}`,
5527
6200
  "",
5528
- "## Validation Commands",
5529
- ...report.validationCommands.length ? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`) : ["- none"],
6201
+ `- Status: ${report.status}`,
6202
+ `- Target: ${report.target ?? "none"}`,
6203
+ "",
6204
+ "## Commands",
6205
+ ...report.commands.length ? report.commands.map((command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`) : ["- none"],
5530
6206
  "",
5531
6207
  "## Diagnostics",
5532
6208
  ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
@@ -5536,154 +6212,17 @@ var renderPrimitiveWriteReport = (report) => [
5536
6212
  ""
5537
6213
  ].join(`
5538
6214
  `);
5539
- var renderPrimitiveReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
5540
- var getSysRoot = (sys2) => `${sys2.type}s/${sys2.name}`;
5541
- var sourceFile = (sys2, path10, action, reason) => ({
5542
- path: `${getSysRoot(sys2)}/${path10}`,
5543
- action,
5544
- reason
5545
- });
5546
- var generatedFilesForSync = (sys2, reason = "Generated files may change after sync.") => generatedFilePathsForTarget(getSysRoot(sys2), reason);
5547
- var validationCommandsForTarget = (target) => [
5548
- { command: `akan sync ${target}`, reason: "Refresh generated Akan files from source conventions." },
5549
- { command: `akan lint ${target}`, reason: "Validate formatting, imports, and static lint rules." }
5550
- ];
5551
- var nextActionsForTarget = (target) => [
5552
- { command: `akan sync ${target}`, reason: "Refresh generated Akan files after source changes." },
5553
- { command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." }
5554
- ];
5555
- var createPassedPrimitiveReport = ({
5556
- command,
5557
- changedFiles,
5558
- generatedFiles,
5559
- target,
5560
- nextActions
5561
- }) => createPrimitiveWriteReport({
5562
- command,
5563
- changedFiles,
5564
- generatedFiles: generatedFiles ?? [],
5565
- validationCommands: validationCommandsForTarget(target),
5566
- diagnostics: [],
5567
- nextActions: nextActions ?? nextActionsForTarget(target)
5568
- });
5569
- var scalarChangedFiles = (sys2, scalarName, files) => Object.values(files).map((file) => sourceFile(sys2, `lib/__scalar/${scalarName}/${file.filename}`, "create", "Scalar source file was created."));
5570
- var titleize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
5571
- var lowerlize = (value) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
5572
- var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!diagnostic);
5573
- var normalizeFieldType = (typeName) => {
5574
- const normalizedTypes = {
5575
- string: "String",
5576
- boolean: "Boolean",
5577
- date: "Date",
5578
- int: "Int",
5579
- integer: "Int",
5580
- float: "Float",
5581
- double: "Float",
5582
- decimal: "Float"
5583
- };
5584
- return normalizedTypes[typeName.toLowerCase()] ?? typeName;
5585
- };
5586
- var ensureBaseTypeImport = (content, typeName) => {
5587
- if (typeName !== "Int" && typeName !== "Float")
5588
- return content;
5589
- if (new RegExp(`import \\{[^}]*\\b${typeName}\\b[^}]*\\} from "akanjs/base";`).test(content))
5590
- return content;
5591
- const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
5592
- if (baseImport) {
5593
- const names = baseImport[1].split(",").map((name) => name.trim()).filter(Boolean);
5594
- return content.replace(baseImport[0], `import { ${[...names, typeName].sort().join(", ")} } from "akanjs/base";`);
5595
- }
5596
- return `import { ${typeName} } from "akanjs/base";
5597
- ${content}`;
5598
- };
5599
- var fieldExpression = (typeName, defaultValue) => {
5600
- const typeExpression = normalizeFieldType(typeName);
5601
- const defaultOption = defaultValue ? `, { default: ${JSON.stringify(defaultValue)} }` : "";
5602
- return `field(${typeExpression}${defaultOption})`;
5603
- };
5604
- var insertIntoObject = (content, className, line) => {
5605
- const classIndex = content.indexOf(`export class ${className} extends via`);
5606
- if (classIndex < 0)
5607
- return null;
5608
- const objectEndIndex = content.indexOf("}))", classIndex);
5609
- if (objectEndIndex < 0)
5610
- return null;
5611
- const prefix = content.slice(0, objectEndIndex);
5612
- const suffix = content.slice(objectEndIndex);
5613
- const insertion = prefix.endsWith(`
5614
- `) ? ` ${line}
5615
- ` : `
5616
- ${line}
5617
- `;
5618
- return `${prefix}${insertion}${suffix}`;
5619
- };
5620
- var ensureEnumImport = (content) => {
5621
- if (content.includes("enumOf"))
5622
- return content;
5623
- const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
5624
- if (baseImport) {
5625
- const names = baseImport[1]?.split(",").map((name) => name.trim()) ?? [];
5626
- return content.replace(baseImport[0], `import { ${[...names, "enumOf"].sort().join(", ")} } from "akanjs/base";`);
5627
- }
5628
- return `import { enumOf } from "akanjs/base";
5629
- ${content}`;
5630
- };
5631
- var insertEnumClass = (content, enumClassName, enumName, values) => {
5632
- if (content.includes(`export class ${enumClassName} extends enumOf`))
5633
- return content;
5634
- const enumClass = `export class ${enumClassName} extends enumOf("${enumName}", [
5635
- ${values.map((value) => ` ${JSON.stringify(value)},`).join(`
5636
- `)}
5637
- ] as const) {}
5638
-
5639
- `;
5640
- const firstClassIndex = content.indexOf("export class ");
5641
- if (firstClassIndex < 0)
5642
- return `${content}
5643
- ${enumClass}`;
5644
- return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
5645
- };
5646
- var insertDictionaryModelField = (content, moduleClassName, fieldName) => {
5647
- if (new RegExp(`\\b${fieldName}\\s*:`).test(content))
5648
- return content;
5649
- const label = titleize(fieldName);
5650
- const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
5651
- if (modelIndex < 0)
5652
- return null;
5653
- const objectEndIndex = content.indexOf(" }))", modelIndex);
5654
- if (objectEndIndex < 0)
5655
- return null;
5656
- return `${content.slice(0, objectEndIndex)} ${fieldName}: t([${JSON.stringify(label)}, ${JSON.stringify(label)}]).desc([${JSON.stringify(label)}, ${JSON.stringify(label)}]),
5657
- ${content.slice(objectEndIndex)}`;
5658
- };
5659
- var ensureConstantTypeImport = (content, constantPath, typeName) => {
5660
- if (new RegExp(`import type \\{[^}]*\\b${typeName}\\b[^}]*\\} from "${constantPath}";`).test(content))
5661
- return content;
5662
- const importPattern = new RegExp(`import type \\{ ([^}]+) \\} from "${constantPath}";`);
5663
- const existingImport = content.match(importPattern);
5664
- if (existingImport !== null) {
5665
- const names = existingImport[1]?.split(",").map((name) => name.trim()) ?? [];
5666
- return content.replace(existingImport[0], `import type { ${[...names, typeName].sort().join(", ")} } from "${constantPath}";`);
6215
+ var renderRepairReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderRepairReportMarkdown(report);
6216
+ var renderWorkflowRunArtifact = (artifact, format = "markdown") => {
6217
+ if ("kind" in artifact)
6218
+ return renderRepairReport(artifact, format);
6219
+ if ("mode" in artifact && artifact.mode === "validate")
6220
+ return renderWorkflowValidation(artifact, format);
6221
+ if ("mode" in artifact && (artifact.mode === "apply" || artifact.mode === "dry-run")) {
6222
+ return renderWorkflowApply(artifact, format);
5667
6223
  }
5668
- return `import type { ${typeName} } from "${constantPath}";
5669
- ${content}`;
5670
- };
5671
- var insertDictionaryEnum = (content, enumClassName, enumName, values) => {
5672
- if (content.includes(`.enum<${enumClassName}>("${enumName}"`))
5673
- return content;
5674
- const enumBlock = ` .enum<${enumClassName}>("${enumName}", (t) => ({
5675
- ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${JSON.stringify(titleize(value))}]),`).join(`
5676
- `)}
5677
- }))
5678
- `;
5679
- const chainEndIndex = content.lastIndexOf(";");
5680
- const insertBeforeIndex = [content.indexOf(".error("), content.indexOf(".translate("), chainEndIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
5681
- if (insertBeforeIndex === undefined)
5682
- return null;
5683
- return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
6224
+ return jsonText(artifact);
5684
6225
  };
5685
- var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
5686
-
5687
6226
  // pkgs/@akanjs/devkit/akanContext.ts
5688
6227
  var resourceList = [
5689
6228
  { uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
@@ -5819,17 +6358,17 @@ var safeReadJson = async (filePath) => {
5819
6358
  var isWorkflowPlan = (value) => typeof value === "object" && value !== null && ("schemaVersion" in value) && value.schemaVersion === 1 && ("mode" in value) && value.mode === "plan";
5820
6359
  var isWorkflowApplyReport = (value) => typeof value === "object" && value !== null && ("schemaVersion" in value) && value.schemaVersion === 1 && ("mode" in value) && (value.mode === "apply" || value.mode === "dry-run");
5821
6360
  var isWorkflowRunArtifact = (value) => typeof value === "object" && value !== null && ("schemaVersion" in value) && value.schemaVersion === 1;
5822
- var planInputString = (plan, key) => {
5823
- const value = plan.inputs[key];
6361
+ var planInputString = (plan2, key) => {
6362
+ const value = plan2.inputs[key];
5824
6363
  return typeof value === "string" ? value : "";
5825
6364
  };
5826
- var expandWorkflowTarget = (target, plan) => {
5827
- const app = planInputString(plan, "app");
5828
- const module = planInputString(plan, "module");
5829
- const moduleClass = module ? capitalize3(module) : "<Module>";
6365
+ var expandWorkflowTarget = (target, plan2) => {
6366
+ const app = planInputString(plan2, "app");
6367
+ const module = planInputString(plan2, "module");
6368
+ const moduleClass = module ? capitalize4(module) : "<Module>";
5830
6369
  return target.replace(/^\*\//, app ? `apps/${app}/` : "").replaceAll("<module>", module || "<module>").replaceAll("<Module>", moduleClass);
5831
6370
  };
5832
- var workflowPathsForPlan = (plan) => plan.predictedChanges.map((change) => expandWorkflowTarget(change.target, plan));
6371
+ var workflowPathsForPlan = (plan2) => plan2.predictedChanges.map((change) => expandWorkflowTarget(change.target, plan2));
5833
6372
  var workflowPathsForArtifact = (artifact) => {
5834
6373
  if (isWorkflowPlan(artifact))
5835
6374
  return workflowPathsForPlan(artifact);
@@ -6044,7 +6583,7 @@ class AkanContextAnalyzer {
6044
6583
  severity: strict ? "error" : "warning",
6045
6584
  code: "module-abstract-missing",
6046
6585
  path: module.abstract.path,
6047
- message: `${capitalize3(module.kind)} module ${sys2.name}:${module.name} should include ${module.abstract.path}`,
6586
+ message: `${capitalize4(module.kind)} module ${sys2.name}:${module.name} should include ${module.abstract.path}`,
6048
6587
  repairActions: [action]
6049
6588
  });
6050
6589
  repairActions.push(action);
@@ -6056,7 +6595,7 @@ class AkanContextAnalyzer {
6056
6595
  severity: "error",
6057
6596
  code: "module-shape-invalid",
6058
6597
  path: module.path,
6059
- message: `${capitalize3(module.kind)} module ${sys2.name}:${module.name} is missing required files: ${missingFiles.join(", ")}`,
6598
+ message: `${capitalize4(module.kind)} module ${sys2.name}:${module.name} is missing required files: ${missingFiles.join(", ")}`,
6060
6599
  repairActions: [action]
6061
6600
  });
6062
6601
  repairActions.push(action);
@@ -6107,8 +6646,8 @@ class AkanContextAnalyzer {
6107
6646
  ...workflowPaths.length ? { baselineDiagnostics, workflowDiagnostics } : {}
6108
6647
  };
6109
6648
  }
6110
- static findModules(context, moduleName) {
6111
- const modules = [...context.apps, ...context.libs].flatMap((sys2) => sys2.modules);
6649
+ static findModules(context, moduleName, { app = null } = {}) {
6650
+ const modules = [...context.apps, ...context.libs].filter((sys2) => !app || sys2.name === app).flatMap((sys2) => sys2.modules);
6112
6651
  return moduleName ? modules.filter((module) => module.name === moduleName || module.folderName === moduleName) : modules;
6113
6652
  }
6114
6653
  static renderMarkdown(context, { module: moduleName } = {}) {
@@ -6298,7 +6837,7 @@ void st;
6298
6837
  ` : `const UserLayout = ({ children }) => children;
6299
6838
  const userLayout = {};
6300
6839
  `;
6301
- const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
6840
+ const source2 = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
6302
6841
  import { loadFonts } from "akanjs/client";
6303
6842
  import { System } from "akanjs/ui";
6304
6843
  import { env } from "@apps/${opts.appName}/env/env.client";
@@ -6374,7 +6913,7 @@ export default function GeneratedLayout({ children, params, searchParams }: Layo
6374
6913
  return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
6375
6914
  }
6376
6915
  `;
6377
- await Bun.write(absPath, source);
6916
+ await Bun.write(absPath, source2);
6378
6917
  return absPath;
6379
6918
  }
6380
6919
  async function resolveSsrPageEntries(opts) {
@@ -6531,23 +7070,23 @@ class BarrelAnalyzer {
6531
7070
  if (visited.has(absFile))
6532
7071
  return;
6533
7072
  visited.add(absFile);
6534
- const source = await readIfExists(absFile);
6535
- if (source === null)
7073
+ const source2 = await readIfExists(absFile);
7074
+ if (source2 === null)
6536
7075
  return;
6537
7076
  const currentSubpath = this.#subpathFor(pkg, absFile);
6538
7077
  if (!currentSubpath)
6539
7078
  return;
6540
- const authoritative = this.#scanExports(source, absFile);
7079
+ const authoritative = this.#scanExports(source2, absFile);
6541
7080
  authoritative.delete("default");
6542
7081
  const attributed = new Set;
6543
7082
  REEXPORT_RE.lastIndex = 0;
6544
- let m = REEXPORT_RE.exec(source);
7083
+ let m = REEXPORT_RE.exec(source2);
6545
7084
  while (m !== null) {
6546
7085
  const star = m[1];
6547
7086
  const nsAs = m[2];
6548
7087
  const namedList = m[3];
6549
7088
  const spec = m[5] ?? "";
6550
- m = REEXPORT_RE.exec(source);
7089
+ m = REEXPORT_RE.exec(source2);
6551
7090
  if (!isRelative(spec))
6552
7091
  continue;
6553
7092
  if (star) {
@@ -6583,10 +7122,10 @@ class BarrelAnalyzer {
6583
7122
  }
6584
7123
  }
6585
7124
  LOCAL_NAMED_RE.lastIndex = 0;
6586
- let n = LOCAL_NAMED_RE.exec(source);
7125
+ let n = LOCAL_NAMED_RE.exec(source2);
6587
7126
  while (n !== null) {
6588
7127
  const body = n[1] ?? "";
6589
- n = LOCAL_NAMED_RE.exec(source);
7128
+ n = LOCAL_NAMED_RE.exec(source2);
6590
7129
  for (const item of parseNamedList(body)) {
6591
7130
  if (item.isType)
6592
7131
  continue;
@@ -6608,10 +7147,10 @@ class BarrelAnalyzer {
6608
7147
  map.set(name, { subpath: currentSubpath, originalName: name });
6609
7148
  }
6610
7149
  }
6611
- #scanExports(source, absFile) {
7150
+ #scanExports(source2, absFile) {
6612
7151
  try {
6613
7152
  const transpiler = [".tsx", ".jsx"].includes(path13.extname(absFile)) ? this.#tsxTranspiler : this.#tsTranspiler;
6614
- const { exports } = transpiler.scan(source);
7153
+ const { exports } = transpiler.scan(source2);
6615
7154
  return new Set(exports);
6616
7155
  } catch (err) {
6617
7156
  this.#logger.error(`scan failed: ${err.message}`);
@@ -6723,28 +7262,28 @@ var createBarrelImportsPlugin = async (app, { skipPath = defaultSkipPath, pipeAf
6723
7262
  const raw = await Bun.file(realPath).text();
6724
7263
  return { contents: raw, loader };
6725
7264
  }
6726
- let source = await Bun.file(realPath).text();
6727
- const hasMacroAttr = MACRO_ATTR_RE.test(source);
7265
+ let source2 = await Bun.file(realPath).text();
7266
+ const hasMacroAttr = MACRO_ATTR_RE.test(source2);
6728
7267
  if (!hasMacroAttr && barrels.length > 0) {
6729
7268
  let maybe = false;
6730
7269
  for (const b of barrels) {
6731
- if (source.includes(b)) {
7270
+ if (source2.includes(b)) {
6732
7271
  maybe = true;
6733
7272
  break;
6734
7273
  }
6735
7274
  }
6736
7275
  if (maybe) {
6737
- const rewritten = await rewriteBarrelImports(source, barrels, analyzer);
7276
+ const rewritten = await rewriteBarrelImports(source2, barrels, analyzer);
6738
7277
  if (rewritten !== null)
6739
- source = rewritten;
7278
+ source2 = rewritten;
6740
7279
  }
6741
7280
  }
6742
7281
  if (pipeAfter) {
6743
- const piped = await pipeAfter(source, { path: realPath });
7282
+ const piped = await pipeAfter(source2, { path: realPath });
6744
7283
  if (piped !== null)
6745
- source = piped;
7284
+ source2 = piped;
6746
7285
  }
6747
- return { contents: source, loader };
7286
+ return { contents: source2, loader };
6748
7287
  });
6749
7288
  }
6750
7289
  };
@@ -6920,12 +7459,12 @@ var resolveFileCandidate = async (candidate) => {
6920
7459
  return null;
6921
7460
  };
6922
7461
  var MACRO_ATTR_RE = /with\s*\{\s*type\s*:\s*["']macro["']\s*\}/;
6923
- var rewriteBarrelImports = async (source, barrels, analyzer) => {
6924
- const statements = findImportStatements(source);
7462
+ var rewriteBarrelImports = async (source2, barrels, analyzer) => {
7463
+ const statements = findImportStatements(source2);
6925
7464
  if (statements.length === 0)
6926
7465
  return null;
6927
7466
  let changed = false;
6928
- let out = source;
7467
+ let out = source2;
6929
7468
  for (let i = statements.length - 1;i >= 0; i--) {
6930
7469
  const stmt = statements[i];
6931
7470
  if (!stmt)
@@ -6943,9 +7482,9 @@ var rewriteBarrelImports = async (source, barrels, analyzer) => {
6943
7482
  }
6944
7483
  return changed ? out : null;
6945
7484
  };
6946
- var findImportStatements = (source) => {
7485
+ var findImportStatements = (source2) => {
6947
7486
  const statements = [];
6948
- const sourceFile2 = ts4.createSourceFile("barrel-imports.tsx", source, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
7487
+ const sourceFile2 = ts4.createSourceFile("barrel-imports.tsx", source2, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
6949
7488
  for (const statement of sourceFile2.statements) {
6950
7489
  if (!ts4.isImportDeclaration(statement))
6951
7490
  continue;
@@ -6959,10 +7498,10 @@ var findImportStatements = (source) => {
6959
7498
  statements.push({
6960
7499
  start: statementStart,
6961
7500
  end: statementEnd,
6962
- clause: source.slice(importClause.getStart(sourceFile2), importClause.end).trim(),
7501
+ clause: source2.slice(importClause.getStart(sourceFile2), importClause.end).trim(),
6963
7502
  specifier: statement.moduleSpecifier.text,
6964
- trailingSemicolon: source.slice(statement.moduleSpecifier.end, statement.end).includes(";"),
6965
- raw: source.slice(statementStart, statementEnd)
7503
+ trailingSemicolon: source2.slice(statement.moduleSpecifier.end, statement.end).includes(";"),
7504
+ raw: source2.slice(statementStart, statementEnd)
6966
7505
  });
6967
7506
  }
6968
7507
  return statements;
@@ -7212,13 +7751,13 @@ class GraphClientEntryDiscovery {
7212
7751
  }
7213
7752
  return cached;
7214
7753
  }
7215
- async#getImports(file, source) {
7754
+ async#getImports(file, source2) {
7216
7755
  const absPath = path15.resolve(file);
7217
7756
  let cached = this.#importCache.get(absPath);
7218
7757
  if (!cached) {
7219
7758
  cached = Promise.resolve().then(() => {
7220
7759
  try {
7221
- return this.#tsTranspiler.scanImports(source);
7760
+ return this.#tsTranspiler.scanImports(source2);
7222
7761
  } catch {
7223
7762
  return [];
7224
7763
  }
@@ -7243,8 +7782,8 @@ class GraphClientEntryDiscovery {
7243
7782
  entries.add(absPath);
7244
7783
  return this.#finishDiscovery(absPath, visiting, entries);
7245
7784
  }
7246
- const source = await this.#getRewrittenSource(absPath, content);
7247
- const imports = await this.#getImports(absPath, source);
7785
+ const source2 = await this.#getRewrittenSource(absPath, content);
7786
+ const imports = await this.#getImports(absPath, source2);
7248
7787
  const importerDir = path15.dirname(absPath);
7249
7788
  for (const imp of imports) {
7250
7789
  const spec = imp.path;
@@ -7280,13 +7819,13 @@ var IMPLICIT_ROOT_LAYOUT_RE = /[/\\]\.akan[/\\]generated[/\\](?:implicit-root-la
7280
7819
  function toClientReferencePath(absPath, workspaceRoot) {
7281
7820
  return path16.relative(path16.resolve(workspaceRoot), path16.resolve(absPath)).split(path16.sep).join("/");
7282
7821
  }
7283
- function transformUseClient(source, args) {
7284
- if (!USE_CLIENT_RE2.test(source))
7822
+ function transformUseClient(source2, args) {
7823
+ if (!USE_CLIENT_RE2.test(source2))
7285
7824
  return null;
7286
7825
  if (IMPLICIT_ROOT_LAYOUT_RE.test(args.path))
7287
7826
  return null;
7288
7827
  const transpiler = new Bun.Transpiler({ loader: loaderFor2(args.path) });
7289
- const { exports } = transpiler.scan(source);
7828
+ const { exports } = transpiler.scan(source2);
7290
7829
  if (exports.length === 0)
7291
7830
  return null;
7292
7831
  const referencePath = args.workspaceRoot ? toClientReferencePath(args.path, args.workspaceRoot) : args.path;
@@ -7432,9 +7971,9 @@ ${absEntry}`).toString(36);
7432
7971
  `;
7433
7972
  }
7434
7973
  async#scanEntryExportNames(absEntry) {
7435
- const source = await Bun.file(absEntry).text();
7974
+ const source2 = await Bun.file(absEntry).text();
7436
7975
  const transpiler = new Bun.Transpiler({ loader: this.#loaderForEntry(absEntry) });
7437
- return transpiler.scan(source).exports;
7976
+ return transpiler.scan(source2).exports;
7438
7977
  }
7439
7978
  #loaderForEntry(absPath) {
7440
7979
  if (absPath.endsWith(".tsx"))
@@ -7481,14 +8020,14 @@ ${absEntry}`).toString(36);
7481
8020
  if (Object.keys(this.#externalAliases).length === 0)
7482
8021
  return;
7483
8022
  await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
7484
- const source = await Bun.file(output.path).text();
7485
- const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source, this.#externalAliases);
7486
- if (rewritten !== source)
8023
+ const source2 = await Bun.file(output.path).text();
8024
+ const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source2, this.#externalAliases);
8025
+ if (rewritten !== source2)
7487
8026
  await Bun.write(output.path, rewritten);
7488
8027
  }));
7489
8028
  }
7490
- static rewriteExternalImportSpecifiers(source, aliases) {
7491
- let rewritten = source;
8029
+ static rewriteExternalImportSpecifiers(source2, aliases) {
8030
+ let rewritten = source2;
7492
8031
  for (const [specifier, alias] of Object.entries(aliases)) {
7493
8032
  if (!alias)
7494
8033
  continue;
@@ -7743,9 +8282,9 @@ ${absEntry}`).toString(36);
7743
8282
  return { buildEntries, originalByBuildEntry };
7744
8283
  }
7745
8284
  async#scanExportNames(absEntry) {
7746
- const source = await Bun.file(absEntry).text();
8285
+ const source2 = await Bun.file(absEntry).text();
7747
8286
  const transpiler = new Bun.Transpiler({ loader: this.#loaderFor(absEntry) });
7748
- return transpiler.scan(source).exports;
8287
+ return transpiler.scan(source2).exports;
7749
8288
  }
7750
8289
  #loaderFor(absPath) {
7751
8290
  if (absPath.endsWith(".tsx"))
@@ -7756,10 +8295,10 @@ ${absEntry}`).toString(36);
7756
8295
  return "ts";
7757
8296
  return "js";
7758
8297
  }
7759
- static normalizeNamedDefaultFunctionForFastRefresh(source) {
8298
+ static normalizeNamedDefaultFunctionForFastRefresh(source2) {
7760
8299
  let changed = false;
7761
8300
  const defaultNames = [];
7762
- const next = source.replace(/(^|\n)(\s*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g, (match, lineStart, indent, asyncKeyword, name) => {
8301
+ const next = source2.replace(/(^|\n)(\s*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g, (match, lineStart, indent, asyncKeyword, name) => {
7763
8302
  changed = true;
7764
8303
  defaultNames.push(name);
7765
8304
  return `${lineStart}${indent}${asyncKeyword ?? ""}function ${name}`;
@@ -7987,8 +8526,8 @@ ${entries.join(`
7987
8526
  }
7988
8527
  static #hasAsyncDefaultExport(moduleAbsPath) {
7989
8528
  try {
7990
- const source = fs3.readFileSync(path21.resolve(moduleAbsPath), "utf8");
7991
- const sourceFile2 = ts5.createSourceFile(moduleAbsPath, source, ts5.ScriptTarget.Latest, true, PagesEntrySourceGenerator.#scriptKind(moduleAbsPath));
8529
+ const source2 = fs3.readFileSync(path21.resolve(moduleAbsPath), "utf8");
8530
+ const sourceFile2 = ts5.createSourceFile(moduleAbsPath, source2, ts5.ScriptTarget.Latest, true, PagesEntrySourceGenerator.#scriptKind(moduleAbsPath));
7992
8531
  return PagesEntrySourceGenerator.#sourceFileHasAsyncDefaultExport(sourceFile2);
7993
8532
  } catch {
7994
8533
  return false;
@@ -8248,8 +8787,8 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
8248
8787
  return html;
8249
8788
  return result + html.slice(lastIndex);
8250
8789
  }
8251
- static escapeInlineScript(source) {
8252
- return source.replace(/<\/script/gi, "<\\/script");
8790
+ static escapeInlineScript(source2) {
8791
+ return source2.replace(/<\/script/gi, "<\\/script");
8253
8792
  }
8254
8793
  static resolveHtmlAssetPath(htmlPath, src) {
8255
8794
  if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
@@ -8480,17 +9019,17 @@ class CssCompiler {
8480
9019
  } catch {
8481
9020
  continue;
8482
9021
  }
8483
- let source = content;
9022
+ let source2 = content;
8484
9023
  if (akanConfig2.barrelImports.length > 0) {
8485
9024
  try {
8486
9025
  const rewritten = await rewriteBarrelImports(content, akanConfig2.barrelImports, analyzer);
8487
9026
  if (rewritten !== null)
8488
- source = rewritten;
9027
+ source2 = rewritten;
8489
9028
  } catch {}
8490
9029
  }
8491
9030
  let imports;
8492
9031
  try {
8493
- imports = this.#transpiler.scanImports(source);
9032
+ imports = this.#transpiler.scanImports(source2);
8494
9033
  } catch {
8495
9034
  continue;
8496
9035
  }
@@ -8929,7 +9468,7 @@ class DevGeneratedIndexSync {
8929
9468
  const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
8930
9469
  if (!rawModel)
8931
9470
  return null;
8932
- const modelName = capitalize4(rawModel);
9471
+ const modelName = capitalize5(rawModel);
8933
9472
  const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
8934
9473
  const fileTypes = [];
8935
9474
  for (const type of allowedTypes) {
@@ -8946,7 +9485,7 @@ export const ${modelName} = { ${fileTypes.join(", ")} };`;
8946
9485
  }
8947
9486
  }
8948
9487
  var exists = async (file) => stat3(file).then(() => true).catch(() => false);
8949
- var capitalize4 = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
9488
+ var capitalize5 = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
8950
9489
  var formatError = (err) => err instanceof Error ? err.message : String(err);
8951
9490
  // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
8952
9491
  import { mkdir as mkdir7 } from "fs/promises";
@@ -9023,8 +9562,8 @@ class FontOptimizer {
9023
9562
  if (faceCss.length > 0)
9024
9563
  this.#cssParts.push(...faceCss, this.#buildRootVariableRule(font));
9025
9564
  }
9026
- #extractFontsExport(source, filePath) {
9027
- const sourceFile2 = ts6.createSourceFile(filePath, source, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TSX);
9565
+ #extractFontsExport(source2, filePath) {
9566
+ const sourceFile2 = ts6.createSourceFile(filePath, source2, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TSX);
9028
9567
  const fonts = [];
9029
9568
  for (const statement of sourceFile2.statements) {
9030
9569
  if (!ts6.isVariableStatement(statement))
@@ -9580,13 +10119,13 @@ function createUseClientBundlePlugin(options = {}) {
9580
10119
  build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
9581
10120
  if (args.path.includes("/node_modules/"))
9582
10121
  return;
9583
- let source;
10122
+ let source2;
9584
10123
  try {
9585
- source = await Bun.file(args.path).text();
10124
+ source2 = await Bun.file(args.path).text();
9586
10125
  } catch {
9587
10126
  return;
9588
10127
  }
9589
- const stubbed = transformUseClient(source, {
10128
+ const stubbed = transformUseClient(source2, {
9590
10129
  path: args.path,
9591
10130
  workspaceRoot: options.workspaceRoot
9592
10131
  });
@@ -9644,7 +10183,7 @@ class PagesBundleBuilder {
9644
10183
  PagesBundleBuilder.createServerUseClientFetchPlugin(),
9645
10184
  await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig2.externalLibs }),
9646
10185
  akanConfig2.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
9647
- pipeAfter: (source, args) => transformUseClient(source, {
10186
+ pipeAfter: (source2, args) => transformUseClient(source2, {
9648
10187
  path: args.path,
9649
10188
  workspaceRoot
9650
10189
  })
@@ -9685,7 +10224,7 @@ class PagesBundleBuilder {
9685
10224
  ...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
9686
10225
  };
9687
10226
  }
9688
- static createPagesEntryPlugin(source) {
10227
+ static createPagesEntryPlugin(source2) {
9689
10228
  return {
9690
10229
  name: "akan-pages-entry",
9691
10230
  setup(build) {
@@ -9694,7 +10233,7 @@ class PagesBundleBuilder {
9694
10233
  namespace: "akan-virtual"
9695
10234
  }));
9696
10235
  build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
9697
- contents: source,
10236
+ contents: source2,
9698
10237
  loader: "tsx"
9699
10238
  }));
9700
10239
  }
@@ -9716,19 +10255,19 @@ class PagesBundleBuilder {
9716
10255
  name: "akan-server-use-client-fetch",
9717
10256
  setup(build) {
9718
10257
  build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
9719
- const source = await Bun.file(args.path).text();
9720
- const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
9721
- if (transformed === source)
10258
+ const source2 = await Bun.file(args.path).text();
10259
+ const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source2);
10260
+ if (transformed === source2)
9722
10261
  return;
9723
10262
  return { contents: transformed, loader: loaderFor4(args.path) };
9724
10263
  });
9725
10264
  }
9726
10265
  };
9727
10266
  }
9728
- static transformServerUseClientFetchSource(source) {
9729
- if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
9730
- return source;
9731
- return source.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
10267
+ static transformServerUseClientFetchSource(source2) {
10268
+ if (!source2.includes(`with { type: "macro" }`) || !source2.includes("FetchClient.build"))
10269
+ return source2;
10270
+ return source2.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
9732
10271
  }
9733
10272
  }
9734
10273
  function loaderFor4(absPath) {
@@ -10494,8 +11033,8 @@ class Builder {
10494
11033
  #executor;
10495
11034
  #distExecutor;
10496
11035
  #pkgJson;
10497
- constructor({ executor, distExecutor, pkgJson }) {
10498
- this.#executor = executor;
11036
+ constructor({ executor: executor2, distExecutor, pkgJson }) {
11037
+ this.#executor = executor2;
10499
11038
  this.#distExecutor = distExecutor;
10500
11039
  this.#pkgJson = pkgJson;
10501
11040
  }
@@ -10621,7 +11160,7 @@ import os from "os";
10621
11160
  import path38 from "path";
10622
11161
  import { select as select2 } from "@inquirer/prompts";
10623
11162
  import { MobileProject } from "@trapezedev/project";
10624
- import { capitalize as capitalize5 } from "akanjs/common";
11163
+ import { capitalize as capitalize6 } from "akanjs/common";
10625
11164
 
10626
11165
  // pkgs/@akanjs/devkit/fileEditor.ts
10627
11166
  class FileEditor {
@@ -11712,7 +12251,7 @@ ${error.message}`;
11712
12251
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
11713
12252
  }
11714
12253
  async#setPermissionInIos(permissions) {
11715
- const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
12254
+ const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize6(key)}`, value]));
11716
12255
  await Promise.all([
11717
12256
  this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
11718
12257
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
@@ -11804,8 +12343,8 @@ import chalk6 from "chalk";
11804
12343
  import { program } from "commander";
11805
12344
 
11806
12345
  // pkgs/@akanjs/devkit/commandDecorators/dependencyBuilder.ts
11807
- var capitalize6 = (value) => value.slice(0, 1).toUpperCase() + value.slice(1);
11808
- var createDependencyKey = (refName, kind) => `${refName}${capitalize6(kind)}`;
12346
+ var capitalize7 = (value) => value.slice(0, 1).toUpperCase() + value.slice(1);
12347
+ var createDependencyKey = (refName, kind) => `${refName}${capitalize7(kind)}`;
11809
12348
 
11810
12349
  class CommandContainer {
11811
12350
  static #instances = new Map;
@@ -12285,13 +12824,13 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
12285
12824
  ...libNames.map((name2) => ({ name: name2, value: { type: "lib", name: name2 } }))
12286
12825
  ]
12287
12826
  });
12288
- const executor = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
12289
- const modules = await executor.getModules();
12827
+ const executor2 = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
12828
+ const modules = await executor2.getModules();
12290
12829
  const moduleName = await select3({
12291
12830
  message: `Select the module name`,
12292
- choices: modules.map((name2) => ({ name: `${executor.name}:${name2}`, value: name2 }))
12831
+ choices: modules.map((name2) => ({ name: `${executor2.name}:${name2}`, value: name2 }))
12293
12832
  });
12294
- return ModuleExecutor.from(executor, moduleName);
12833
+ return ModuleExecutor.from(executor2, moduleName);
12295
12834
  } else
12296
12835
  throw new Error(`Invalid system type: ${argMeta.type}`);
12297
12836
  };
@@ -12543,6 +13082,8 @@ var NODE_NATIVE_MODULE_SET = new Set([
12543
13082
  ]);
12544
13083
  // pkgs/@akanjs/devkit/getCredentials.ts
12545
13084
  import yaml from "js-yaml";
13085
+ // pkgs/@akanjs/devkit/getModelFileData.ts
13086
+ import { capitalize as capitalize8 } from "akanjs/common";
12546
13087
  // pkgs/@akanjs/devkit/getRelatedCnsts.ts
12547
13088
  import { readFileSync as readFileSync4, realpathSync } from "fs";
12548
13089
  import ora2 from "ora";
@@ -12550,18 +13091,18 @@ import * as ts7 from "typescript";
12550
13091
  var tsTranspiler = new Bun.Transpiler({ loader: "ts" });
12551
13092
  var tsxTranspiler = new Bun.Transpiler({ loader: "tsx" });
12552
13093
  var getTranspiler = (filePath) => filePath.endsWith(".tsx") ? tsxTranspiler : tsTranspiler;
12553
- var scanModuleSpecifiers = (source, filePath, includeExports) => {
12554
- const scannedImports = getTranspiler(filePath).scanImports(source).map((imp) => imp.path).filter(Boolean);
13094
+ var scanModuleSpecifiers = (source2, filePath, includeExports) => {
13095
+ const scannedImports = getTranspiler(filePath).scanImports(source2).map((imp) => imp.path).filter(Boolean);
12555
13096
  if (includeExports) {
12556
13097
  const specifiers = new Set(scannedImports);
12557
13098
  const typeOnlyModuleRegex = /\b(?:import|export)\s+type\s+[\s\S]*?\s+from\s*["']([^"']+)["']/g;
12558
- for (const match of source.matchAll(typeOnlyModuleRegex)) {
13099
+ for (const match of source2.matchAll(typeOnlyModuleRegex)) {
12559
13100
  const importPath = match[1];
12560
13101
  if (importPath)
12561
13102
  specifiers.add(importPath);
12562
13103
  }
12563
13104
  const exportFromRegex = /\bexport\s+(?:type\s+)?(?:\*|{[\s\S]*?})\s+from\s*["']([^"']+)["']/g;
12564
- for (const match of source.matchAll(exportFromRegex)) {
13105
+ for (const match of source2.matchAll(exportFromRegex)) {
12565
13106
  const importPath = match[1];
12566
13107
  if (importPath)
12567
13108
  specifiers.add(importPath);
@@ -12570,7 +13111,7 @@ var scanModuleSpecifiers = (source, filePath, includeExports) => {
12570
13111
  }
12571
13112
  const importSpecifiers = new Set;
12572
13113
  const importDeclarationRegex = /\bimport\s+(?:type\s+)?(?:["']([^"']+)["']|[\s\S]*?\s+from\s*["']([^"']+)["'])/g;
12573
- for (const match of source.matchAll(importDeclarationRegex)) {
13114
+ for (const match of source2.matchAll(importDeclarationRegex)) {
12574
13115
  const importPath = match[1] ?? match[2];
12575
13116
  if (importPath && (scannedImports.includes(importPath) || match[0].startsWith("import type"))) {
12576
13117
  importSpecifiers.add(importPath);
@@ -12593,8 +13134,8 @@ var collectImportedFiles = (constantFilePath, parsedConfig) => {
12593
13134
  if (analyzedFiles.has(filePath))
12594
13135
  return;
12595
13136
  analyzedFiles.add(filePath);
12596
- const source = readFileSync4(filePath, "utf-8");
12597
- for (const importPath of scanModuleSpecifiers(source, filePath, false)) {
13137
+ const source2 = readFileSync4(filePath, "utf-8");
13138
+ for (const importPath of scanModuleSpecifiers(source2, filePath, false)) {
12598
13139
  if (!importPath.startsWith("."))
12599
13140
  continue;
12600
13141
  const resolved = ts7.resolveModuleName(importPath, filePath, parsedConfig.options, ts7.sys).resolvedModule?.resolvedFileName;
@@ -12643,21 +13184,21 @@ var analyzeProperties = (filesToAnalyze, program2, checker) => {
12643
13184
  if (analyzedFiles.has(filePath))
12644
13185
  return;
12645
13186
  analyzedFiles.add(filePath);
12646
- const source = program2.getSourceFile(filePath);
12647
- if (!source)
13187
+ const source2 = program2.getSourceFile(filePath);
13188
+ if (!source2)
12648
13189
  return;
12649
13190
  if (!sourceLineCache.has(filePath)) {
12650
- sourceLineCache.set(filePath, source.getFullText().split(`
13191
+ sourceLineCache.set(filePath, source2.getFullText().split(`
12651
13192
  `));
12652
13193
  }
12653
13194
  const sourceLines = sourceLineCache.get(filePath);
12654
13195
  function visit(node) {
12655
- if (!source)
13196
+ if (!source2)
12656
13197
  return;
12657
13198
  if (ts7.isPropertyAccessExpression(node)) {
12658
13199
  const left = node.expression;
12659
13200
  const right = node.name;
12660
- const { line } = ts7.getLineAndCharacterOfPosition(source, node.getStart());
13201
+ const { line } = ts7.getLineAndCharacterOfPosition(source2, node.getStart());
12661
13202
  if (ts7.isIdentifier(left) && sourceLines && sourceLines.length > line && sourceLines[line] && (sourceLines[line]?.includes(`@Field.Prop(() => ${left.text}.${right.text}`) || sourceLines[line].includes(`base.Filter(${left.text}.${right.text},`))) {
12662
13203
  const symbol = getCachedSymbol(right);
12663
13204
  if (symbol?.declarations && symbol.declarations.length > 0) {
@@ -12708,7 +13249,7 @@ var analyzeProperties = (filesToAnalyze, program2, checker) => {
12708
13249
  }
12709
13250
  ts7.forEachChild(node, visit);
12710
13251
  }
12711
- visit(source);
13252
+ visit(source2);
12712
13253
  }
12713
13254
  for (const filePath of filesToAnalyze) {
12714
13255
  analyzeFileProperties(filePath);
@@ -14112,8 +14653,8 @@ class CloudRunner extends runner("cloud") {
14112
14653
  #getSshTarget(config) {
14113
14654
  return `${config.username ? `${config.username}@` : ""}${config.host}`;
14114
14655
  }
14115
- #getScpArgs(config, source, target) {
14116
- return [...config.port ? ["-P", config.port.toString()] : [], source, target];
14656
+ #getScpArgs(config, source2, target) {
14657
+ return [...config.port ? ["-P", config.port.toString()] : [], source2, target];
14117
14658
  }
14118
14659
  #getSshArgs(config, command3) {
14119
14660
  return [...config.port ? ["-p", config.port.toString()] : [], this.#getSshTarget(config), command3];
@@ -14493,7 +15034,7 @@ import path43 from "path";
14493
15034
 
14494
15035
  // pkgs/@akanjs/cli/module/module.script.ts
14495
15036
  import { input as input6 } from "@inquirer/prompts";
14496
- import { capitalize as capitalize9, randomPicks } from "akanjs/common";
15037
+ import { capitalize as capitalize11, randomPicks } from "akanjs/common";
14497
15038
 
14498
15039
  // pkgs/@akanjs/cli/page/page.runner.ts
14499
15040
  class PageRunner extends runner("page") {
@@ -14523,7 +15064,7 @@ function pluralizeName(name) {
14523
15064
  }
14524
15065
 
14525
15066
  // pkgs/@akanjs/cli/module/module.request.ts
14526
- import { capitalize as capitalize7 } from "akanjs/common";
15067
+ import { capitalize as capitalize9 } from "akanjs/common";
14527
15068
 
14528
15069
  // pkgs/@akanjs/cli/module/module.prompt.ts
14529
15070
  var componentDefaultDescription = ({
@@ -14648,7 +15189,7 @@ var requestTemplate = ({
14648
15189
  ${componentDefaultDescription({
14649
15190
  sysName,
14650
15191
  modelName,
14651
- ModelName: ModelName ?? capitalize7(modelName),
15192
+ ModelName: ModelName ?? capitalize9(modelName),
14652
15193
  exampleFiles,
14653
15194
  constant,
14654
15195
  properties
@@ -14705,7 +15246,7 @@ var requestView = ({
14705
15246
  ${componentDefaultDescription({
14706
15247
  sysName,
14707
15248
  modelName,
14708
- ModelName: ModelName ?? capitalize7(modelName),
15249
+ ModelName: ModelName ?? capitalize9(modelName),
14709
15250
  exampleFiles,
14710
15251
  constant,
14711
15252
  properties
@@ -14766,7 +15307,7 @@ var requestUnit = ({
14766
15307
  ${componentDefaultDescription({
14767
15308
  sysName,
14768
15309
  modelName,
14769
- ModelName: ModelName ?? capitalize7(modelName),
15310
+ ModelName: ModelName ?? capitalize9(modelName),
14770
15311
  exampleFiles,
14771
15312
  constant,
14772
15313
  properties
@@ -14815,7 +15356,47 @@ var requestUnit = ({
14815
15356
  `;
14816
15357
 
14817
15358
  // pkgs/@akanjs/cli/module/module.runner.ts
14818
- import { capitalize as capitalize8 } from "akanjs/common";
15359
+ import { capitalize as capitalize10 } from "akanjs/common";
15360
+ var purposeByModule = {
15361
+ budget: "Budget represents planned or actual money allocated inside the app.",
15362
+ project: "Project represents a project workspace or business initiative managed by the app.",
15363
+ task: "Task represents work items that move through the app workflow."
15364
+ };
15365
+ var moduleAbstractContent = (moduleName) => {
15366
+ const title = capitalize10(moduleName);
15367
+ const label = bilingualLabelForField(moduleName);
15368
+ const purpose = purposeByModule[moduleName] ?? `${title} represents ${label.en.toLowerCase()} records managed by the app.`;
15369
+ return `# ${title} Module Abstract
15370
+
15371
+ ## Purpose
15372
+
15373
+ ${purpose}
15374
+
15375
+ ## Domain Rules
15376
+
15377
+ - Keep ${label.en.toLowerCase()} data consistent with user-facing dictionary labels.
15378
+
15379
+ ## Data Meaning
15380
+
15381
+ ${label.en} (${label.ko}) is the primary business concept for this module.
15382
+
15383
+ ## Workflows
15384
+
15385
+ No lifecycle workflow yet.
15386
+
15387
+ ## Agent Notes
15388
+
15389
+ - Read this abstract before changing the module.
15390
+ - Update this file when business invariants, workflows, or public behavior change.
15391
+ - Do not update this file for formatting-only, import-only, or style-only changes.
15392
+
15393
+ ## Related Modules
15394
+
15395
+ - None yet.
15396
+ `;
15397
+ };
15398
+ var localModuleFilename = (moduleName, pathKey2) => moduleSourcePaths(moduleName)[pathKey2].replace(`lib/${moduleName}/`, "");
15399
+
14819
15400
  class ModuleRunner extends runner("module") {
14820
15401
  async createService(module) {
14821
15402
  const serviceName = module.name.replace(/^_+/, "");
@@ -14845,23 +15426,47 @@ class ModuleRunner extends runner("module") {
14845
15426
  async createComponentTemplate(module, type) {
14846
15427
  await module.sys.applyTemplate({
14847
15428
  basePath: `./lib/${module.name}`,
14848
- template: `module/__Model__.${capitalize8(type)}.tsx`,
15429
+ template: `module/__Model__.${capitalize10(type)}.tsx`,
14849
15430
  dict: { model: module.name, appName: module.sys.name }
14850
15431
  });
14851
15432
  return {
14852
15433
  component: {
14853
- filename: `${module.name}.${capitalize8(type)}.tsx`,
14854
- content: await module.sys.readFile(`lib/${module.name}/${capitalize8(module.name)}.${capitalize8(type)}.tsx`)
15434
+ filename: `${capitalize10(module.name)}.${capitalize10(type)}.tsx`,
15435
+ content: await module.sys.readFile(`lib/${module.name}/${capitalize10(module.name)}.${capitalize10(type)}.tsx`)
14855
15436
  }
14856
15437
  };
14857
15438
  }
14858
15439
  async createModuleTemplate(module) {
14859
15440
  const names = pluralizeName(module.name);
15441
+ const modelLabel = bilingualLabelForField(module.name);
15442
+ const modelDescription = bilingualDescriptionForField(module.name);
15443
+ const filenames = {
15444
+ abstract: localModuleFilename(module.name, "abstract"),
15445
+ constant: localModuleFilename(module.name, "constant"),
15446
+ dictionary: localModuleFilename(module.name, "dictionary"),
15447
+ service: localModuleFilename(module.name, "service"),
15448
+ store: localModuleFilename(module.name, "store"),
15449
+ signal: localModuleFilename(module.name, "signal"),
15450
+ unit: localModuleFilename(module.name, "unit"),
15451
+ view: localModuleFilename(module.name, "view"),
15452
+ template: localModuleFilename(module.name, "template"),
15453
+ zone: localModuleFilename(module.name, "zone"),
15454
+ util: localModuleFilename(module.name, "util")
15455
+ };
14860
15456
  await module.applyTemplate({
14861
15457
  basePath: `.`,
14862
15458
  template: "module",
14863
- dict: { model: module.name, models: names, sysName: module.sys.name }
15459
+ dict: {
15460
+ model: module.name,
15461
+ models: names,
15462
+ sysName: module.sys.name,
15463
+ modelLabelEn: modelLabel.en,
15464
+ modelLabelKo: modelLabel.ko,
15465
+ modelDescEn: modelDescription.en,
15466
+ modelDescKo: modelDescription.ko
15467
+ }
14864
15468
  });
15469
+ await module.writeFile(filenames.abstract, moduleAbstractContent(module.name));
14865
15470
  const [
14866
15471
  abstractContent,
14867
15472
  constantContent,
@@ -14875,30 +15480,30 @@ class ModuleRunner extends runner("module") {
14875
15480
  zoneContent,
14876
15481
  utilContent
14877
15482
  ] = await Promise.all([
14878
- module.readFile(`${module.name}.abstract.md`),
14879
- module.readFile(`${module.name}.constant.ts`),
14880
- module.readFile(`${module.name}.dictionary.ts`),
14881
- module.readFile(`${module.name}.service.ts`),
14882
- module.readFile(`${module.name}.store.ts`),
14883
- module.readFile(`${module.name}.signal.ts`),
14884
- module.readFile(`${module.name}.Unit.tsx`),
14885
- module.readFile(`${module.name}.View.tsx`),
14886
- module.readFile(`${module.name}.Template.tsx`),
14887
- module.readFile(`${module.name}.Zone.tsx`),
14888
- module.readFile(`${module.name}.Util.tsx`)
15483
+ module.readFile(filenames.abstract),
15484
+ module.readFile(filenames.constant),
15485
+ module.readFile(filenames.dictionary),
15486
+ module.readFile(filenames.service),
15487
+ module.readFile(filenames.store),
15488
+ module.readFile(filenames.signal),
15489
+ module.readFile(filenames.unit),
15490
+ module.readFile(filenames.view),
15491
+ module.readFile(filenames.template),
15492
+ module.readFile(filenames.zone),
15493
+ module.readFile(filenames.util)
14889
15494
  ]);
14890
15495
  return {
14891
- abstract: { filename: `${module.name}.abstract.md`, content: abstractContent },
14892
- constant: { filename: `${module.name}.constant.ts`, content: constantContent },
14893
- dictionary: { filename: `${module.name}.dictionary.ts`, content: dictionaryContent },
14894
- service: { filename: `${module.name}.service.ts`, content: serviceContent },
14895
- store: { filename: `${module.name}.store.ts`, content: storeContent },
14896
- signal: { filename: `${module.name}.signal.ts`, content: signalContent },
14897
- unit: { filename: `${module.name}.Unit.tsx`, content: unitContent },
14898
- view: { filename: `${module.name}.View.tsx`, content: viewContent },
14899
- template: { filename: `${module.name}.Template.tsx`, content: templateContent },
14900
- zone: { filename: `${module.name}.Zone.tsx`, content: zoneContent },
14901
- util: { filename: `${module.name}.Util.tsx`, content: utilContent }
15496
+ abstract: { filename: filenames.abstract, content: abstractContent },
15497
+ constant: { filename: filenames.constant, content: constantContent },
15498
+ dictionary: { filename: filenames.dictionary, content: dictionaryContent },
15499
+ service: { filename: filenames.service, content: serviceContent },
15500
+ store: { filename: filenames.store, content: storeContent },
15501
+ signal: { filename: filenames.signal, content: signalContent },
15502
+ unit: { filename: filenames.unit, content: unitContent },
15503
+ view: { filename: filenames.view, content: viewContent },
15504
+ template: { filename: filenames.template, content: templateContent },
15505
+ zone: { filename: filenames.zone, content: zoneContent },
15506
+ util: { filename: filenames.util, content: utilContent }
14902
15507
  };
14903
15508
  }
14904
15509
  }
@@ -14928,11 +15533,11 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
14928
15533
  const isContinued = await sys3.exists(`lib/${name}/${name}.constant.ts`);
14929
15534
  const session = new AiSession("createModule", { workspace: sys3.workspace, cacheKey: name, isContinued });
14930
15535
  const moduleConstantExampleFiles = await sys3.workspace.getConstantFiles();
14931
- const executor = ModuleExecutor.from(sys3, name);
14932
- const files = await this.moduleRunner.createModuleTemplate(executor);
15536
+ const executor2 = ModuleExecutor.from(sys3, name);
15537
+ const files = await this.moduleRunner.createModuleTemplate(executor2);
14933
15538
  const { constant, dictionary } = files;
14934
15539
  if (page && sys3.type === "app")
14935
- await this.pageScript.createCrudPage(executor, { app: sys3, basePath: null, single: false });
15540
+ await this.pageScript.createCrudPage(executor2, { app: sys3, basePath: null, single: false });
14936
15541
  const modelDesc = description ?? await input6({ message: "description of module" });
14937
15542
  const modelSchemaDesign = schemaDescription ?? await input6({ message: "schema description of module" });
14938
15543
  const config = await sys3.getConfig();
@@ -15000,8 +15605,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
15000
15605
  async createTest(workspace, name) {}
15001
15606
  async createTemplate(mod) {
15002
15607
  const { component: template } = await this.moduleRunner.createComponentTemplate(mod, "template");
15003
- const templateExampleFiles = (await mod.sys.getTemplatesSourceCode()).filter((f) => !f.filePath.includes(`${mod.name}.Template.tsx`));
15004
- const Name = capitalize9(mod.name);
15608
+ const paths = moduleSourcePaths(mod.name);
15609
+ const templateExampleFiles = (await mod.sys.getTemplatesSourceCode()).filter((f) => !f.filePath.includes(paths.template));
15610
+ const Name = capitalize11(mod.name);
15005
15611
  const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
15006
15612
  const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
15007
15613
  const session = new AiSession("createTemplate", { workspace: mod.sys.workspace, cacheKey: mod.name });
@@ -15027,8 +15633,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
15027
15633
  }
15028
15634
  async createUnit(mod) {
15029
15635
  const { component: unit } = await this.moduleRunner.createComponentTemplate(mod, "unit");
15030
- const Name = capitalize9(mod.name);
15031
- const unitExampleFiles = (await mod.sys.getUnitsSourceCode()).filter((f) => !f.filePath.includes(`${mod.name}.Unit.tsx`));
15636
+ const paths = moduleSourcePaths(mod.name);
15637
+ const Name = capitalize11(mod.name);
15638
+ const unitExampleFiles = (await mod.sys.getUnitsSourceCode()).filter((f) => !f.filePath.includes(paths.unit));
15032
15639
  const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
15033
15640
  const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
15034
15641
  const session = new AiSession("createUnit", { workspace: mod.sys.workspace, cacheKey: mod.name });
@@ -15052,8 +15659,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
15052
15659
  }
15053
15660
  async createView(mod) {
15054
15661
  const { component: view } = await this.moduleRunner.createComponentTemplate(mod, "view");
15055
- const viewExampleFiles = (await mod.sys.getViewsSourceCode()).filter((f) => !f.filePath.includes(`${mod.name}.View.tsx`));
15056
- const Name = capitalize9(mod.name);
15662
+ const paths = moduleSourcePaths(mod.name);
15663
+ const viewExampleFiles = (await mod.sys.getViewsSourceCode()).filter((f) => !f.filePath.includes(paths.view));
15664
+ const Name = capitalize11(mod.name);
15057
15665
  const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
15058
15666
  const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
15059
15667
  const session = new AiSession("createView", { workspace: mod.sys.workspace, cacheKey: mod.name });
@@ -15078,7 +15686,7 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
15078
15686
  }
15079
15687
 
15080
15688
  // pkgs/@akanjs/cli/primitive/primitive.script.ts
15081
- import { capitalize as capitalize10 } from "akanjs/common";
15689
+ import { capitalize as capitalize12 } from "akanjs/common";
15082
15690
  class PrimitiveScript extends script("primitive", [ModuleScript]) {
15083
15691
  async resolveSys(workspace, target) {
15084
15692
  if (!target)
@@ -15126,7 +15734,7 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
15126
15734
  }
15127
15735
  async addEnumField(workspace, input7) {
15128
15736
  const values = parseValues(input7.values);
15129
- return await this.addFieldToSources(workspace, { ...input7, type: `${capitalize10(input7.field ?? "")}` }, { enumValues: values });
15737
+ return await this.addFieldToSources(workspace, { ...input7, type: `${capitalize12(input7.field ?? "")}` }, { enumValues: values });
15130
15738
  }
15131
15739
  async addFieldToSources(workspace, input7, { enumValues }) {
15132
15740
  const sys3 = await this.resolveSys(workspace, input7.app);
@@ -15170,10 +15778,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
15170
15778
  nextActions: []
15171
15779
  });
15172
15780
  }
15173
- const moduleClassName = capitalize10(input7.module);
15781
+ const moduleClassName = capitalize12(input7.module);
15174
15782
  const inputClassName = `${moduleClassName}Input`;
15175
- const constantPath = `lib/${input7.module}/${input7.module}.constant.ts`;
15176
- const dictionaryPath = `lib/${input7.module}/${input7.module}.dictionary.ts`;
15783
+ const paths = moduleSourcePaths(input7.module);
15784
+ const constantPath = paths.constant;
15785
+ const dictionaryPath = paths.dictionary;
15786
+ const templatePath = paths.template;
15177
15787
  const changedFiles = [];
15178
15788
  const generatedFiles2 = generatedFilesForSync(sys3);
15179
15789
  const [hasConstant, hasDictionary] = await Promise.all([sys3.exists(constantPath), sys3.exists(dictionaryPath)]);
@@ -15212,8 +15822,8 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
15212
15822
  });
15213
15823
  }
15214
15824
  if (enumValues) {
15215
- const enumClassName = `${moduleClassName}${capitalize10(input7.field)}`;
15216
- const enumName = `${lowerlize(moduleClassName)}${capitalize10(input7.field)}`;
15825
+ const enumClassName = `${moduleClassName}${capitalize12(input7.field)}`;
15826
+ const enumName = `${lowerlize(moduleClassName)}${capitalize12(input7.field)}`;
15217
15827
  constantContent = insertEnumClass(ensureEnumImport(constantContent), enumClassName, enumName, enumValues);
15218
15828
  dictionaryContent = ensureConstantTypeImport(dictionaryContent, `./${input7.module}.constant`, enumClassName);
15219
15829
  input7.type = enumClassName;
@@ -15230,10 +15840,36 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
15230
15840
  }
15231
15841
  if (!enumValues && normalizedType) {
15232
15842
  input7.type = normalizedType;
15843
+ const defaultCoercion = coerceFieldDefault(input7.type, input7.defaultValue);
15844
+ if (defaultCoercion.diagnostic)
15845
+ diagnostics.push(defaultCoercion.diagnostic);
15233
15846
  constantContent = ensureBaseTypeImport(constantContent, input7.type);
15234
15847
  }
15235
- const nextConstantContent = insertIntoObject(constantContent, inputClassName, `${input7.field}: ${fieldExpression(input7.type, input7.defaultValue)},`);
15848
+ if (enumValues) {
15849
+ const defaultCoercion = coerceFieldDefault("enum", input7.defaultValue, { enumValues });
15850
+ if (defaultCoercion.diagnostic)
15851
+ diagnostics.push(defaultCoercion.diagnostic);
15852
+ }
15853
+ const nextConstantContent = insertIntoObject(constantContent, inputClassName, `${input7.field}: ${fieldExpression(input7.type, input7.defaultValue, { enumValues })},`);
15854
+ const nextConstantContentWithLight = nextConstantContent && input7.includeInLight ? insertLightProjectionField(nextConstantContent, moduleClassName, input7.field) : nextConstantContent;
15236
15855
  const nextDictionaryContent = insertDictionaryModelField(dictionaryContent, moduleClassName, input7.field);
15856
+ if (nextConstantContentWithLight && (input7.type === "Int" || input7.type === "Float") && new RegExp(`\\b${input7.field}\\s*:\\s*field\\(${input7.type}, \\{ default: "`, "m").test(nextConstantContentWithLight)) {
15857
+ diagnostics.push({
15858
+ severity: "error",
15859
+ code: "primitive-default-value-invalid",
15860
+ input: "default",
15861
+ failureScope: "source-change",
15862
+ message: `Generated ${input7.type} default for "${input7.field}" would be a string literal; refusing to write source.`
15863
+ });
15864
+ }
15865
+ if (nextConstantContentWithLight && (input7.type === "Int" || input7.type === "Float") && !new RegExp(`import \\{[^}]*\\b${input7.type}\\b[^}]*\\} from "akanjs/base";`).test(nextConstantContentWithLight)) {
15866
+ diagnostics.push({
15867
+ severity: "error",
15868
+ code: "primitive-base-type-import-missing",
15869
+ failureScope: "source-change",
15870
+ message: `Generated source for ${input7.field} requires ${input7.type} import from "akanjs/base".`
15871
+ });
15872
+ }
15237
15873
  if (!nextConstantContent) {
15238
15874
  diagnostics.push({
15239
15875
  severity: "error",
@@ -15241,6 +15877,14 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
15241
15877
  message: `Could not find ${inputClassName} object shape in ${constantPath}.`
15242
15878
  });
15243
15879
  }
15880
+ if (nextConstantContent && input7.includeInLight && !nextConstantContentWithLight) {
15881
+ diagnostics.push({
15882
+ severity: "warning",
15883
+ code: "primitive-light-projection-shape-unsupported",
15884
+ failureScope: "source-change",
15885
+ message: `Could not find a safe Light${moduleClassName} projection insertion point in ${constantPath}.`
15886
+ });
15887
+ }
15244
15888
  if (!nextDictionaryContent) {
15245
15889
  diagnostics.push({
15246
15890
  severity: "error",
@@ -15248,10 +15892,51 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
15248
15892
  message: `Could not find ${moduleClassName} dictionary model shape in ${dictionaryPath}.`
15249
15893
  });
15250
15894
  }
15251
- if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextConstantContent && nextDictionaryContent) {
15252
- await sys3.writeFile(constantPath, nextConstantContent);
15895
+ let nextTemplateContent;
15896
+ if (input7.surfaces?.includes("template")) {
15897
+ const policy = addFieldUiPolicyForType(enumValues ? "enum" : input7.type);
15898
+ const hasTemplate = await sys3.exists(templatePath);
15899
+ if (!hasTemplate) {
15900
+ diagnostics.push({
15901
+ severity: "warning",
15902
+ code: "primitive-template-missing",
15903
+ failureScope: "source-change",
15904
+ message: `Template source file was not found: ${templatePath}. Auto-edit skipped; add the field to the Template form manually if this module renders one.`
15905
+ });
15906
+ } else if (!policy.autoTemplateSupported || policy.component === "Field.ToggleSelect") {
15907
+ diagnostics.push({
15908
+ severity: "warning",
15909
+ code: "primitive-template-component-manual",
15910
+ failureScope: "source-change",
15911
+ message: `Template auto insertion for ${policy.component} is not supported because option binding must be confirmed. Candidate position: inside Layout.Template near existing Field components in ${templatePath}.`
15912
+ });
15913
+ } else {
15914
+ const templateContent = await sys3.readFile(templatePath);
15915
+ nextTemplateContent = insertTemplateField({
15916
+ content: templateContent,
15917
+ moduleName: input7.module,
15918
+ moduleClassName,
15919
+ fieldName: input7.field,
15920
+ component: policy.component
15921
+ });
15922
+ if (!nextTemplateContent) {
15923
+ diagnostics.push({
15924
+ severity: "warning",
15925
+ code: "primitive-template-shape-unsupported",
15926
+ failureScope: "source-change",
15927
+ message: `Could not find a safe Template insertion point in ${templatePath}. Expected generated ${input7.module}Form hook and Layout.Template closing tag; candidate position is the existing field list before </Layout.Template>.`
15928
+ });
15929
+ }
15930
+ }
15931
+ }
15932
+ if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextConstantContentWithLight && nextDictionaryContent) {
15933
+ await sys3.writeFile(constantPath, nextConstantContentWithLight);
15253
15934
  await sys3.writeFile(dictionaryPath, nextDictionaryContent);
15254
15935
  changedFiles.push(sourceFile(sys3, constantPath, "modify", "Field source shape was updated."), sourceFile(sys3, dictionaryPath, "modify", "Field dictionary labels were updated."));
15936
+ if (nextTemplateContent !== undefined && nextTemplateContent !== null) {
15937
+ await sys3.writeFile(templatePath, nextTemplateContent);
15938
+ changedFiles.push(sourceFile(sys3, templatePath, "modify", "Template field surface was updated."));
15939
+ }
15255
15940
  }
15256
15941
  return createPrimitiveWriteReport({
15257
15942
  command: enumValues ? "add-enum-field" : "add-field",
@@ -15312,8 +15997,8 @@ class RepairRunner extends runner("repair") {
15312
15997
  format = "markdown",
15313
15998
  execute
15314
15999
  }) {
15315
- const executor = execute ?? defaultExecutor(workspace);
15316
- const report = await this.createRepairReport(kind, { workspace, app, module, target, execute: executor });
16000
+ const executor2 = execute ?? defaultExecutor(workspace);
16001
+ const report = await this.createRepairReport(kind, { workspace, app, module, target, execute: executor2 });
15317
16002
  const { artifact: artifact2, runId } = await writeWorkflowRunArtifact(workspace, report);
15318
16003
  const reportWithArtifact = artifact2;
15319
16004
  if (kind === "generated" && reportWithArtifact.status === "passed" && reportWithArtifact.target) {
@@ -15620,6 +16305,7 @@ class ScalarScript extends script("scalar", [ScalarRunner]) {
15620
16305
  // pkgs/@akanjs/cli/workflow/workflow.runner.ts
15621
16306
  import { mkdir as mkdir12, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
15622
16307
  import path42 from "path";
16308
+ import { capitalize as capitalize13 } from "akanjs/common";
15623
16309
 
15624
16310
  // pkgs/@akanjs/cli/workflows/shared.ts
15625
16311
  var sysInputs = {
@@ -15733,7 +16419,18 @@ var addFieldWorkflowSpec = {
15733
16419
  description: "Field type or scalar name. Use Int for integer fields or Float for decimal fields; do not use Number."
15734
16420
  },
15735
16421
  values: { type: "string-list", description: "Comma-separated enum values when type is enum." },
15736
- default: { type: "string", description: "Optional default value for the field." }
16422
+ default: {
16423
+ type: "string",
16424
+ description: "Optional default value. plan/apply coerce by field type: Int/Float to numeric literals, Boolean to true/false, Date to new Date(...), String/scalar to string literals, and enum only when the value is in values."
16425
+ },
16426
+ surfaces: {
16427
+ type: "string-list",
16428
+ description: "Optional UI surfaces to update, comma-separated. Use template to auto-update simple generated Template forms; View/Unit stay planned as manual review."
16429
+ },
16430
+ includeInLight: {
16431
+ type: "boolean",
16432
+ description: "Whether to add the field to the Light<Model> projection used by list/card displays."
16433
+ }
15737
16434
  },
15738
16435
  optionalSurfaces: {
15739
16436
  dictionary: "include",
@@ -16180,7 +16877,7 @@ var failedPlan = (workflow2, diagnostics) => ({
16180
16877
  recommendations: [],
16181
16878
  requiresApproval: true
16182
16879
  });
16183
- var failedApplyReport = (workflow2, diagnostics, plan) => createWorkflowApplyReport({
16880
+ var failedApplyReport = (workflow2, diagnostics, plan2) => createWorkflowApplyReport({
16184
16881
  workflow: workflow2,
16185
16882
  mode: "apply",
16186
16883
  changedFiles: [],
@@ -16188,7 +16885,7 @@ var failedApplyReport = (workflow2, diagnostics, plan) => createWorkflowApplyRep
16188
16885
  commands: [],
16189
16886
  diagnostics,
16190
16887
  nextActions: [],
16191
- plan: plan ?? failedPlan(workflow2, diagnostics)
16888
+ plan: plan2 ?? failedPlan(workflow2, diagnostics)
16192
16889
  });
16193
16890
  var commandForShell2 = (command3) => command3.startsWith("akan ") ? `bun run ${command3}` : command3;
16194
16891
  var inferValidationKind = (command3) => {
@@ -16248,6 +16945,61 @@ var defaultValidationExecutor = (workspace) => async (command3) => {
16248
16945
  }
16249
16946
  };
16250
16947
  var readJsonFile = async (filePath) => JSON.parse(await readFile2(resolvePath(filePath), "utf8"));
16948
+ var planInputString2 = (plan2, key) => {
16949
+ const value = plan2.inputs[key];
16950
+ return typeof value === "string" ? value : "";
16951
+ };
16952
+ var workflowPathsForPlanLike = (plan2) => {
16953
+ const app = planInputString2(plan2, "app");
16954
+ const module = planInputString2(plan2, "module");
16955
+ const moduleClass = module ? capitalize13(module) : "<Module>";
16956
+ return plan2.predictedChanges.map((change) => change.target.replace(/^\*\//, app ? `apps/${app}/` : "").replaceAll("<module>", module || "<module>").replaceAll("<Module>", moduleClass));
16957
+ };
16958
+ var workflowDiagnosticFromDoctor = (diagnostic, fallbackScope) => ({
16959
+ severity: diagnostic.severity,
16960
+ code: diagnostic.code,
16961
+ message: diagnostic.message,
16962
+ scope: diagnostic.scope ?? fallbackScope,
16963
+ failureScope: (diagnostic.scope ?? fallbackScope) === "baseline" ? "workspace-config" : (diagnostic.scope ?? fallbackScope) === "workflow" ? "source-change" : "unknown",
16964
+ context: diagnostic.context
16965
+ });
16966
+ var baselineBlockerCachePath = (workflow2) => `.akan/workflows/baseline/${workflow2}.json`;
16967
+ var blockerFingerprint = (blocker) => [blocker.failureScope, blocker.code, blocker.command ?? "", blocker.kind ?? "", blocker.message].join("|");
16968
+ var applyBaselineBlockerCache = async (workspace, report) => {
16969
+ const cacheableBlockers = report.knownBlockers.filter((blocker) => blocker.failureScope === "workspace-config" || blocker.failureScope === "environment");
16970
+ if (cacheableBlockers.length === 0)
16971
+ return report;
16972
+ const cachePath = baselineBlockerCachePath(report.workflow);
16973
+ const cached = (await workspace.exists(cachePath) ? await workspace.readJson(cachePath) : null) ?? { schemaVersion: 1, workflow: report.workflow, blockers: [] };
16974
+ const knownFingerprints = new Set(cached.blockers.map((blocker) => blocker.fingerprint));
16975
+ const now = new Date().toISOString();
16976
+ const blockers = new Map(cached.blockers.map((blocker) => [blocker.fingerprint, blocker]));
16977
+ for (const blocker of cacheableBlockers) {
16978
+ const fingerprint = blockerFingerprint(blocker);
16979
+ blockers.set(fingerprint, {
16980
+ fingerprint,
16981
+ code: blocker.code,
16982
+ message: blocker.message,
16983
+ failureScope: blocker.failureScope,
16984
+ command: blocker.command,
16985
+ kind: blocker.kind,
16986
+ lastSeenAt: now
16987
+ });
16988
+ }
16989
+ await workspace.writeFile(cachePath, jsonText({ schemaVersion: 1, workflow: report.workflow, blockers: [...blockers.values()] }), { silent: true });
16990
+ return {
16991
+ ...report,
16992
+ knownBlockers: report.knownBlockers.map((blocker) => {
16993
+ if (!knownFingerprints.has(blockerFingerprint(blocker)))
16994
+ return blocker;
16995
+ return {
16996
+ ...blocker,
16997
+ known: true,
16998
+ message: `Known baseline blocker, unrelated to this source change: ${blocker.message}`
16999
+ };
17000
+ })
17001
+ };
17002
+ };
16251
17003
 
16252
17004
  class WorkflowRunner extends runner("workflow") {
16253
17005
  list({ format = "markdown" } = {}) {
@@ -16269,13 +17021,13 @@ class WorkflowRunner extends runner("workflow") {
16269
17021
  const spec = getWorkflowSpec(workflowSpecs, workflow2);
16270
17022
  if (!spec)
16271
17023
  throw new Error(`Unknown workflow: ${workflow2}. Run \`akan workflow list\` to see available workflows.`);
16272
- const plan = createWorkflowPlan(spec, compactWorkflowInputs(inputs));
17024
+ const plan2 = createWorkflowPlan(spec, compactWorkflowInputs(inputs));
16273
17025
  if (out) {
16274
17026
  const outPath = resolvePath(out);
16275
17027
  await mkdir12(path42.dirname(outPath), { recursive: true });
16276
- await writeFile2(outPath, jsonText(plan));
17028
+ await writeFile2(outPath, jsonText(plan2));
16277
17029
  }
16278
- return format === "json" ? jsonText(plan) : renderWorkflowPlan(plan);
17030
+ return format === "json" ? jsonText(plan2) : renderWorkflowPlan(plan2);
16279
17031
  }
16280
17032
  async apply(planPath, {
16281
17033
  dryRun = false,
@@ -16289,7 +17041,7 @@ class WorkflowRunner extends runner("workflow") {
16289
17041
  const { artifact: artifact2 } = await writeWorkflowRunArtifact(workspace, report);
16290
17042
  return renderWorkflowApply(artifact2, format);
16291
17043
  };
16292
- let plan;
17044
+ let plan2;
16293
17045
  try {
16294
17046
  const parsed = JSON.parse(await readFile2(resolvePath(planPath), "utf8"));
16295
17047
  if (!isWorkflowPlan2(parsed)) {
@@ -16302,7 +17054,7 @@ class WorkflowRunner extends runner("workflow") {
16302
17054
  ]);
16303
17055
  return await renderApplyReport(report);
16304
17056
  }
16305
- plan = parsed;
17057
+ plan2 = parsed;
16306
17058
  } catch (error) {
16307
17059
  const report = failedApplyReport("unknown", [
16308
17060
  {
@@ -16313,30 +17065,30 @@ class WorkflowRunner extends runner("workflow") {
16313
17065
  ]);
16314
17066
  return await renderApplyReport(report);
16315
17067
  }
16316
- const spec = getWorkflowSpec(workflowSpecs, plan.workflow);
17068
+ const spec = getWorkflowSpec(workflowSpecs, plan2.workflow);
16317
17069
  if (!spec) {
16318
- const report = failedApplyReport(plan.workflow, [
17070
+ const report = failedApplyReport(plan2.workflow, [
16319
17071
  {
16320
17072
  severity: "error",
16321
17073
  code: "workflow-unknown",
16322
- message: `Unknown workflow in plan: ${plan.workflow}.`
17074
+ message: `Unknown workflow in plan: ${plan2.workflow}.`
16323
17075
  }
16324
- ], plan);
17076
+ ], plan2);
16325
17077
  return await renderApplyReport(report);
16326
17078
  }
16327
17079
  if (dryRun)
16328
- return await renderApplyReport(createDryRunWorkflowApplyReport(plan));
17080
+ return await renderApplyReport(createDryRunWorkflowApplyReport(plan2));
16329
17081
  if (!registry) {
16330
- const report = failedApplyReport(plan.workflow, [
17082
+ const report = failedApplyReport(plan2.workflow, [
16331
17083
  {
16332
17084
  severity: "error",
16333
17085
  code: "workflow-registry-missing",
16334
17086
  message: "Workflow apply requires a step runner registry."
16335
17087
  }
16336
- ], plan);
17088
+ ], plan2);
16337
17089
  return await renderApplyReport(report);
16338
17090
  }
16339
- return await renderApplyReport(await new WorkflowExecutor(registry).apply(plan));
17091
+ return await renderApplyReport(await new WorkflowExecutor(registry).apply(plan2));
16340
17092
  }
16341
17093
  async validate(runIdOrPlan, {
16342
17094
  format = "markdown",
@@ -16344,15 +17096,22 @@ class WorkflowRunner extends runner("workflow") {
16344
17096
  execute
16345
17097
  }) {
16346
17098
  const loaded = await this.loadValidationTarget(runIdOrPlan, workspace);
16347
- const report = await createWorkflowValidationRunReport({
17099
+ const doctor = await AkanContextAnalyzer.doctor(workspace, {
17100
+ strict: true,
17101
+ runIdOrPlan,
17102
+ changedFiles: loaded.changedFiles
17103
+ });
17104
+ const report = await applyBaselineBlockerCache(workspace, await createWorkflowValidationRunReport({
16348
17105
  workflow: loaded.plan?.workflow ?? loaded.workflow,
16349
17106
  source: loaded.source,
16350
17107
  plan: loaded.plan,
16351
17108
  commands: loaded.commands,
16352
17109
  execute: execute ?? defaultValidationExecutor(workspace),
16353
17110
  diagnostics: loaded.diagnostics,
17111
+ baselineDiagnostics: (doctor.baselineDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "baseline")),
17112
+ workflowDiagnostics: (doctor.workflowDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "workflow")),
16354
17113
  repairActions: loaded.repairActions
16355
- });
17114
+ }));
16356
17115
  await writeWorkflowRunArtifact(workspace, report);
16357
17116
  return renderWorkflowValidation(report, format);
16358
17117
  }
@@ -16391,6 +17150,7 @@ class WorkflowRunner extends runner("workflow") {
16391
17150
  plan: artifact2.plan,
16392
17151
  commands: artifact2.recommendedValidationCommands.length ? artifact2.recommendedValidationCommands : workflowCommandsForPlan(artifact2.plan),
16393
17152
  diagnostics: artifact2.diagnostics,
17153
+ changedFiles: artifact2.changedFiles.map((file) => file.path),
16394
17154
  repairActions: []
16395
17155
  };
16396
17156
  }
@@ -16401,6 +17161,7 @@ class WorkflowRunner extends runner("workflow") {
16401
17161
  plan: artifact2.plan,
16402
17162
  commands: artifact2.plan ? workflowCommandsForPlan(artifact2.plan) : [],
16403
17163
  diagnostics: artifact2.diagnostics,
17164
+ changedFiles: [],
16404
17165
  repairActions: artifact2.repairActions
16405
17166
  };
16406
17167
  }
@@ -16416,6 +17177,7 @@ class WorkflowRunner extends runner("workflow") {
16416
17177
  message: `Workflow validation source is not supported: ${runIdOrPlan}.`
16417
17178
  }
16418
17179
  ],
17180
+ changedFiles: [],
16419
17181
  repairActions: []
16420
17182
  };
16421
17183
  };
@@ -16428,6 +17190,7 @@ class WorkflowRunner extends runner("workflow") {
16428
17190
  plan: parsed,
16429
17191
  commands: workflowCommandsForPlan(parsed),
16430
17192
  diagnostics: parsed.diagnostics,
17193
+ changedFiles: workflowPathsForPlanLike(parsed),
16431
17194
  repairActions: []
16432
17195
  };
16433
17196
  }
@@ -16450,6 +17213,7 @@ class WorkflowRunner extends runner("workflow") {
16450
17213
  message: `Could not read workflow validation source: ${runIdOrPlan}. ${error instanceof Error ? error.message : ""}`.trim()
16451
17214
  }
16452
17215
  ],
17216
+ changedFiles: [],
16453
17217
  repairActions: []
16454
17218
  };
16455
17219
  }
@@ -16516,7 +17280,8 @@ var readonlyMcpTools = [
16516
17280
  { name: "list_modules", inputSchema: emptySchema },
16517
17281
  {
16518
17282
  name: "get_module_context",
16519
- inputSchema: { type: "object", properties: { module: stringProperty }, required: ["module"] }
17283
+ description: "Return module context. Pass app in monorepos to disambiguate duplicate module names.",
17284
+ inputSchema: { type: "object", properties: { app: stringProperty, module: stringProperty }, required: ["module"] }
16520
17285
  },
16521
17286
  {
16522
17287
  name: "get_guideline",
@@ -16651,6 +17416,7 @@ class ContextRunner extends runner("context") {
16651
17416
  }
16652
17417
  if (name === "get_workspace_summary" || name === "list_apps" || name === "list_modules" || name === "get_module_context") {
16653
17418
  const context = await AkanContextAnalyzer.analyze(workspace, {
17419
+ app: args.app ?? null,
16654
17420
  module: args.module ?? null,
16655
17421
  includeAbstractContent: name === "get_module_context"
16656
17422
  });
@@ -16660,7 +17426,28 @@ class ContextRunner extends runner("context") {
16660
17426
  return context.apps;
16661
17427
  if (name === "list_modules")
16662
17428
  return AkanContextAnalyzer.findModules(context);
16663
- return AkanContextAnalyzer.findModules(context, args.module);
17429
+ const modules = AkanContextAnalyzer.findModules(context, args.module, {
17430
+ app: args.app ?? null
17431
+ });
17432
+ if (name === "get_module_context" && !args.app && modules.length > 1) {
17433
+ return {
17434
+ diagnostics: [
17435
+ {
17436
+ severity: "error",
17437
+ code: "module-context-ambiguous",
17438
+ message: `Multiple modules match "${args.module}". Re-run get_module_context with an app argument.`,
17439
+ input: "app"
17440
+ }
17441
+ ],
17442
+ candidates: modules.map((module) => ({
17443
+ app: module.sysName,
17444
+ sysType: module.sysType,
17445
+ module: module.name,
17446
+ path: module.path
17447
+ }))
17448
+ };
17449
+ }
17450
+ return modules;
16664
17451
  }
16665
17452
  if (name === "get_guideline")
16666
17453
  return await Prompter.getInstruction(stringArg(args, "name"));
@@ -16689,6 +17476,16 @@ class ContextRunner extends runner("context") {
16689
17476
  validationFailureScopes: ["workspace-config", "environment", "source-change", "unknown"],
16690
17477
  artifactChainFields: ["planPath", "applyReportPath", "repairReportPath", "runId", "validationTarget", "next"],
16691
17478
  diagnosticScopes: ["baseline", "workflow", "unknown"],
17479
+ validationStatuses: {
17480
+ sourceStatus: ["passed", "failed", "unknown"],
17481
+ workspaceStatus: ["passed", "failed", "unknown"],
17482
+ overallStatus: ["passed", "failed", "blocked-by-workspace-config", "blocked-by-environment"],
17483
+ knownBlockers: "Repeated workspace-config/environment failures are summarized before command output."
17484
+ },
17485
+ moduleContextInputs: {
17486
+ module: "required",
17487
+ app: "recommended in monorepos; required when module names are ambiguous"
17488
+ },
16692
17489
  generatedFreshnessStatuses: ["fresh", "stale", "missing", "unknown"],
16693
17490
  directEditFallbackPolicy: applyFirstPolicy,
16694
17491
  applyReportFields: [
@@ -16714,12 +17511,12 @@ class ContextRunner extends runner("context") {
16714
17511
  const workflow2 = stringArg(args, "workflow");
16715
17512
  const inputs = workflowInputsArg(args);
16716
17513
  const planPath = typeof args.out === "string" && args.out ? args.out : defaultWorkflowPlanPath(workflow2, inputs);
16717
- const plan = parseJsonOutput(await new WorkflowRunner().plan(workflow2, inputs, {
17514
+ const plan2 = parseJsonOutput(await new WorkflowRunner().plan(workflow2, inputs, {
16718
17515
  format: "json",
16719
17516
  out: workspacePath(workspace, planPath)
16720
17517
  }));
16721
17518
  return {
16722
- ...plan,
17519
+ ...plan2,
16723
17520
  planPath,
16724
17521
  next: { tool: "apply_workflow", args: { planPath } },
16725
17522
  policy: applyFirstPolicy