@akanjs/cli 2.3.9-rc.8 → 2.3.9

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.
@@ -2,7 +2,7 @@
2
2
  var __require = import.meta.require;
3
3
 
4
4
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
5
- import path42 from "path";
5
+ import path44 from "path";
6
6
 
7
7
  // pkgs/@akanjs/devkit/aiEditor.ts
8
8
  import { input, select } from "@inquirer/prompts";
@@ -4799,6 +4799,11 @@ var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!d
4799
4799
 
4800
4800
  // pkgs/@akanjs/devkit/workflow/artifacts.ts
4801
4801
  var sourceChangeBlocked = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown"));
4802
+ var workflowPlanApproval = {
4803
+ required: true,
4804
+ meaning: "Review this read-only plan before apply_workflow mutates files.",
4805
+ applyTool: "apply_workflow"
4806
+ };
4802
4807
  var inferNextActionCode = (action, diagnostics) => {
4803
4808
  if (action.action)
4804
4809
  return action.action;
@@ -4843,13 +4848,19 @@ var createWorkflowApplyReport = ({
4843
4848
  });
4844
4849
  }
4845
4850
  const orderedNextActions = uniqueBy(nextActionsWithIntent, (action) => action.command).sort((left, right) => nextActionPriority(left, diagnostics) - nextActionPriority(right, diagnostics));
4851
+ const sourceFilesChanged = uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`);
4852
+ const generatedFilesSynced = uniqueBy(generatedFiles, (file) => `${file.action}:${file.path}:${file.reason}`);
4846
4853
  return {
4847
4854
  schemaVersion: 1,
4848
4855
  workflow,
4849
4856
  mode,
4850
4857
  status: workflowStatus(diagnostics),
4851
- changedFiles: uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
4852
- generatedFiles: uniqueBy(generatedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
4858
+ summary: {
4859
+ sourceFilesChanged,
4860
+ generatedFilesSynced
4861
+ },
4862
+ changedFiles: sourceFilesChanged,
4863
+ generatedFiles: generatedFilesSynced,
4853
4864
  appliedCommands: uniqueBy(appliedCommands, (command) => command.command),
4854
4865
  recommendedValidationCommands: uniqueBy(validationCommands, (command) => command.command),
4855
4866
  commands: uniqueBy(validationCommands, (command) => command.command),
@@ -4927,6 +4938,52 @@ var statusForValidationKind = (commands, kind) => {
4927
4938
  return "unknown";
4928
4939
  return matching.some((command) => command.status === "failed") ? "failed" : "passed";
4929
4940
  };
4941
+ var statusForCommands = (commands) => {
4942
+ if (commands.length === 0)
4943
+ return "unknown";
4944
+ return commandStatus(commands) === "failed" ? "failed" : "passed";
4945
+ };
4946
+ var statusForDiagnostics = (diagnostics) => {
4947
+ if (diagnostics.length === 0)
4948
+ return "unknown";
4949
+ return workflowStatus(diagnostics) === "failed" ? "failed" : "passed";
4950
+ };
4951
+ var workflowDiagnosticContextPaths = (diagnostics) => uniqueBy(diagnostics.flatMap((diagnostic) => diagnostic.context?.paths ?? []), (filePath) => filePath);
4952
+ var createWorkflowBaselineSummary = (diagnostics, { detailsIncluded = true, knownBlockerCount = 0 } = {}) => {
4953
+ const grouped = new Map;
4954
+ let totalErrors = 0;
4955
+ let totalWarnings = 0;
4956
+ for (const diagnostic of diagnostics) {
4957
+ if (diagnostic.severity === "error")
4958
+ totalErrors += 1;
4959
+ else
4960
+ totalWarnings += 1;
4961
+ const existing = grouped.get(diagnostic.code);
4962
+ if (existing) {
4963
+ existing.count += 1;
4964
+ if (existing.severity !== diagnostic.severity)
4965
+ existing.severity = "mixed";
4966
+ } else {
4967
+ grouped.set(diagnostic.code, {
4968
+ code: diagnostic.code,
4969
+ severity: diagnostic.severity,
4970
+ count: 1,
4971
+ sampleMessage: diagnostic.message
4972
+ });
4973
+ }
4974
+ }
4975
+ const contextPaths = workflowDiagnosticContextPaths(diagnostics);
4976
+ return {
4977
+ status: totalErrors > 0 ? "failed" : diagnostics.length > 0 ? "passed" : "unknown",
4978
+ total: diagnostics.length,
4979
+ totalErrors,
4980
+ totalWarnings,
4981
+ detailsIncluded,
4982
+ knownBlockerCount,
4983
+ byCode: [...grouped.values()],
4984
+ ...contextPaths.length ? { contextPaths } : {}
4985
+ };
4986
+ };
4930
4987
  var createKnownBlockers = (commands, diagnostics) => {
4931
4988
  const commandBlockers = commands.filter((command) => command.status === "failed" && (command.failureScope === "workspace-config" || command.failureScope === "environment")).map((command) => ({
4932
4989
  code: `workflow-validation-${command.failureScope}`,
@@ -4955,18 +5012,28 @@ var createKnownBlockers = (commands, diagnostics) => {
4955
5012
  }
4956
5013
  return [...grouped.values()];
4957
5014
  };
4958
- var createValidationStatuses = (commands, diagnostics) => {
5015
+ var createValidationStatuses = (commands, reportDiagnostics, baselineDiagnostics, workflowDiagnostics) => {
5016
+ const diagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
4959
5017
  const scopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(diagnostics)];
4960
- const sourceStatus = statusForScope(commands, diagnostics, scopes, "source-change");
5018
+ const sourceDiagnostics = [...reportDiagnostics, ...workflowDiagnostics].filter((diagnostic) => diagnostic.failureScope === "source-change" || diagnostic.scope === "workflow" || !diagnostic.failureScope && diagnostic.scope !== "baseline");
5019
+ const sourceScopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(sourceDiagnostics)];
5020
+ const sourceStatus = statusForScope(commands, sourceDiagnostics, sourceScopes, "source-change");
5021
+ const validationCommandsStatus = statusForCommands(commands);
5022
+ const baselineStatus = statusForDiagnostics(baselineDiagnostics);
5023
+ const nonBaselineDiagnostics = [...reportDiagnostics, ...workflowDiagnostics];
4961
5024
  const workspaceStatus = hasScopeFailure(scopes, "workspace-config", diagnostics) || hasScopeFailure(scopes, "environment", diagnostics) ? "failed" : commands.length || diagnostics.length ? "passed" : "unknown";
4962
- 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";
5025
+ const overallStatus = hasScopeFailure(scopes, "source-change", diagnostics) ? "failed" : validationCommandsStatus === "passed" && baselineStatus === "failed" && workflowStatus(nonBaselineDiagnostics) !== "failed" ? "passed-with-baseline-blockers" : hasScopeFailure(scopes, "workspace-config", diagnostics) ? "blocked-by-workspace-config" : hasScopeFailure(scopes, "environment", diagnostics) ? "blocked-by-environment" : workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed";
4963
5026
  return {
4964
5027
  sourceStatus,
4965
5028
  workspaceStatus,
5029
+ validationCommandsStatus,
5030
+ baselineStatus,
4966
5031
  overallStatus,
4967
5032
  summary: {
4968
5033
  sourceChange: sourceStatus,
4969
5034
  generatedSync: statusForValidationKind(commands, "sync"),
5035
+ validationCommands: validationCommandsStatus,
5036
+ baseline: baselineStatus,
4970
5037
  workspaceConfig: statusForScope(commands, diagnostics, scopes, "workspace-config"),
4971
5038
  environment: statusForScope(commands, diagnostics, scopes, "environment")
4972
5039
  }
@@ -5000,7 +5067,8 @@ var createWorkflowValidationRunReport = async ({
5000
5067
  ] : []);
5001
5068
  const reportDiagnostics = [...diagnostics, ...commandDiagnostics];
5002
5069
  const scopedDiagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
5003
- const statuses = createValidationStatuses(results, scopedDiagnostics);
5070
+ const knownBlockers = createKnownBlockers(results, scopedDiagnostics);
5071
+ const statuses = createValidationStatuses(results, reportDiagnostics, baselineDiagnostics, workflowDiagnostics);
5004
5072
  return {
5005
5073
  schemaVersion: 1,
5006
5074
  runId,
@@ -5009,9 +5077,12 @@ var createWorkflowValidationRunReport = async ({
5009
5077
  source,
5010
5078
  status: statuses.overallStatus === "passed" ? "passed" : "failed",
5011
5079
  ...statuses,
5012
- knownBlockers: createKnownBlockers(results, scopedDiagnostics),
5080
+ knownBlockers,
5013
5081
  commands: results,
5014
5082
  diagnostics: reportDiagnostics,
5083
+ baselineSummary: createWorkflowBaselineSummary(baselineDiagnostics, {
5084
+ knownBlockerCount: knownBlockers.filter((blocker) => blocker.failureScope === "workspace-config").length
5085
+ }),
5015
5086
  baselineDiagnostics,
5016
5087
  workflowDiagnostics,
5017
5088
  repairActions: uniqueBy(repairActions, (action) => action.command),
@@ -6327,7 +6398,7 @@ var checkTypeScriptSyntax = async (workspace, filePath) => {
6327
6398
  return null;
6328
6399
  const content = await workspace.readFile(filePath);
6329
6400
  const source = ts6.createSourceFile(filePath, content, ts6.ScriptTarget.Latest, true, scriptKind);
6330
- const diagnostic = source.parseDiagnostics[0];
6401
+ const diagnostic = (source.parseDiagnostics ?? [])[0];
6331
6402
  if (!diagnostic)
6332
6403
  return null;
6333
6404
  const position = diagnostic.file?.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
@@ -6525,9 +6596,9 @@ var addFieldUiSurfaceInspection = (plan) => {
6525
6596
  code: "add-field-ui-surface-review",
6526
6597
  kind: "manual-action",
6527
6598
  target,
6528
- 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.`,
6599
+ action: templateRequested ? `Template was requested for ${field}. If no Template file changed, users will not see the field in the form yet because the file was missing, the generated ${module}Form/Layout.Template pattern was not found, or ${policy.component} needs option binding. Add it inside Layout.Template near existing Field components.` : `Template was not selected, so users will not see ${field} in the form from this apply. If list/card display is needed, include ${field} in Light${moduleClassName} projection data and place it in the local Unit/View card layout.`,
6529
6600
  confidence: "medium",
6530
- message: `Review UI surfaces for ${module}.${field}; recommended component is ${policy.component}, with manual reasons and candidate positions in action.`
6601
+ message: `Review user-visible UI for ${module}.${field}; recommended component is ${policy.component}.`
6531
6602
  }
6532
6603
  ],
6533
6604
  nextActions: [
@@ -6782,10 +6853,48 @@ var createAddFieldDefaultRecommendations = (inputs) => {
6782
6853
  kind: "manual-action",
6783
6854
  confidence: "high",
6784
6855
  message: `Default for ${field} will be normalized to ${coercion.normalizedType} literal ${coercion.expression}.`,
6785
- action: "Review the normalized default in the apply report if this value should stay unset."
6856
+ action: "Review the normalized default in the apply report. To leave the field without a default, omit the default input."
6786
6857
  }
6787
6858
  ];
6788
6859
  };
6860
+ var moneyLikeFieldPattern = /(budget|price|amount|cost|rate|fee|salary|balance)/i;
6861
+ var wholeNumberFieldPattern = /(count|quantity|total|number|index|rank|order|age)/i;
6862
+ var createAddFieldInputRecommendations = (inputs) => {
6863
+ const field = typeof inputs.field === "string" ? inputs.field : "<field>";
6864
+ const typeName = typeof inputs.type === "string" ? inputs.type : null;
6865
+ const recommendations = [];
6866
+ if (!typeName)
6867
+ return recommendations;
6868
+ if (typeName.toLowerCase() === "number" || typeName.toLowerCase() === "numeric") {
6869
+ const recommendedType = moneyLikeFieldPattern.test(field) ? "Float" : wholeNumberFieldPattern.test(field) ? "Int" : null;
6870
+ recommendations.push({
6871
+ code: "add-field-type-choice",
6872
+ kind: "input-guidance",
6873
+ confidence: recommendedType ? "high" : "medium",
6874
+ message: recommendedType ? `Use ${recommendedType} for ${field}; Float is recommended for money-like decimal values and Int for whole-number values.` : `Choose Int for whole-number ${field} values or Float for decimal ${field} values.`,
6875
+ action: recommendedType ? `Re-run plan_workflow with type="${recommendedType}".` : 'Re-run plan_workflow with type="Int" or type="Float".'
6876
+ });
6877
+ }
6878
+ if (typeName.toLowerCase() === "enum" && !Array.isArray(inputs.values)) {
6879
+ recommendations.push({
6880
+ code: "add-field-enum-values",
6881
+ kind: "input-guidance",
6882
+ confidence: "high",
6883
+ message: `Enum field ${field} needs values before apply can choose valid dictionary and default behavior.`,
6884
+ action: 'Pass values as an array or comma-separated string, for example values=["draft","done"].'
6885
+ });
6886
+ }
6887
+ if (inputs.default === undefined) {
6888
+ recommendations.push({
6889
+ code: "add-field-default-optional",
6890
+ kind: "input-guidance",
6891
+ confidence: "medium",
6892
+ message: `No default will be written for ${field} because default is omitted.`,
6893
+ action: "Keep default omitted when the field should have no default value."
6894
+ });
6895
+ }
6896
+ return recommendations;
6897
+ };
6789
6898
  var createAddFieldRecommendations = (inputs) => {
6790
6899
  const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
6791
6900
  const module = typeof inputs.module === "string" ? inputs.module : "<module>";
@@ -6846,9 +6955,9 @@ var createAddFieldRecommendations = (inputs) => {
6846
6955
  code: "add-field-light-projection-choice",
6847
6956
  kind: "manual-action",
6848
6957
  target: paths.constant,
6849
- action: `Pass includeInLight=true when ${field} should appear in Light${capitalize2(module)} list/card projections.`,
6958
+ action: `Pass includeInLight=true when users should see ${field} in list/card data. Without it, the field can exist on ${capitalize2(module)}Input but stay absent from Light${capitalize2(module)} projections.`,
6850
6959
  confidence: "medium",
6851
- message: `Light projection exposure for ${module}.${field} is not selected yet.`
6960
+ message: `${module}.${field} is not selected for list/card projection data.`
6852
6961
  }
6853
6962
  ] : [],
6854
6963
  ...includeInLight ? [
@@ -6866,18 +6975,20 @@ var createAddFieldRecommendations = (inputs) => {
6866
6975
  kind: "manual-action",
6867
6976
  target: paths.template,
6868
6977
  confidence: "medium",
6869
- message: `Template is not included in surfaces, so ${module}.${field} will not be auto-rendered there.`
6978
+ message: `Template form auto-edit is skipped for ${module}.${field} because surfaces does not include "template".`,
6979
+ action: `Pass surfaces=["template"] when users should enter ${field} in the Template form.`
6870
6980
  }
6871
6981
  ] : [],
6872
6982
  {
6873
6983
  code: "add-field-ui-manual-review",
6874
6984
  kind: "manual-action",
6875
6985
  target: paths.template,
6876
- 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${capitalize2(module)} projection array for list data, and Unit/View card sections for display.`,
6986
+ action: `After apply, verify what users can see: Template form auto-edit only runs when a generated ${module}Form hook and Layout.Template field list are present. Unit/View cards are not auto-edited, so ${field} may be stored on ${capitalize2(module)} but not shown in list/card UI until you place it in the local card layout.`,
6877
6987
  confidence: "medium",
6878
- message: "UI manual review includes the reason auto-edit may be skipped and candidate insertion positions."
6988
+ message: `${app}:${module}.${field} may need UI review for user-visible Template, Unit, and View behavior.`
6879
6989
  },
6880
- ...createAddFieldDefaultRecommendations(inputs)
6990
+ ...createAddFieldDefaultRecommendations(inputs),
6991
+ ...createAddFieldInputRecommendations(inputs)
6881
6992
  ];
6882
6993
  };
6883
6994
  var createWorkflowPlanPredictedChanges = (spec, inputs) => {
@@ -7020,7 +7131,8 @@ var createWorkflowPlan = (spec, rawInputs) => {
7020
7131
  validation: spec.validation,
7021
7132
  diagnostics,
7022
7133
  recommendations: createWorkflowPlanRecommendations(spec, inputs),
7023
- requiresApproval: true
7134
+ requiresApproval: true,
7135
+ approval: workflowPlanApproval
7024
7136
  };
7025
7137
  };
7026
7138
  // pkgs/@akanjs/devkit/workflow/render.ts
@@ -7058,6 +7170,7 @@ var renderWorkflowPlan = (plan) => [
7058
7170
  "",
7059
7171
  `- Mode: ${plan.mode}`,
7060
7172
  `- Requires approval: ${plan.requiresApproval}`,
7173
+ `- Approval: ${plan.approval?.meaning ?? "Review this read-only plan before applying workflow mutations."}`,
7061
7174
  "",
7062
7175
  "## Inputs",
7063
7176
  ...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
@@ -7094,6 +7207,10 @@ var renderDiagnostic = (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.
7094
7207
  var renderNextAction = (action) => `- \`${action.command}\`: ${action.reason}`;
7095
7208
  var applySourceStatus = (report) => report.diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown")) ? "failed" : "passed";
7096
7209
  var renderWorkflowApplyReport = (report) => {
7210
+ const applySummary = {
7211
+ sourceFilesChanged: report.summary?.sourceFilesChanged ?? report.changedFiles,
7212
+ generatedFilesSynced: report.summary?.generatedFilesSynced ?? report.generatedFiles
7213
+ };
7097
7214
  const manualReviewItems = [
7098
7215
  ...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
7099
7216
  ...report.recommendations.filter((recommendation) => recommendation.kind === "manual-action").map(renderRecommendation)
@@ -7107,6 +7224,8 @@ var renderWorkflowApplyReport = (report) => {
7107
7224
  `- Status: ${report.status}`,
7108
7225
  `- Source-change status: ${applySourceStatus(report)}`,
7109
7226
  `- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
7227
+ `- Source files changed: ${applySummary.sourceFilesChanged.length}`,
7228
+ `- Generated files queued for sync: ${applySummary.generatedFilesSynced.length}`,
7110
7229
  ...report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : [],
7111
7230
  "",
7112
7231
  "## Apply Checks",
@@ -7116,10 +7235,10 @@ var renderWorkflowApplyReport = (report) => {
7116
7235
  ...sourceBlockers.length ? sourceBlockers : ["- none"],
7117
7236
  "",
7118
7237
  "## Automatically Modified",
7119
- ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
7238
+ ...applySummary.sourceFilesChanged.length ? applySummary.sourceFilesChanged.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
7120
7239
  "",
7121
7240
  "## Generated Sync",
7122
- ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
7241
+ ...applySummary.generatedFilesSynced.length ? applySummary.generatedFilesSynced.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
7123
7242
  "",
7124
7243
  "## Applied Commands",
7125
7244
  ...report.appliedCommands.length ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
@@ -7146,52 +7265,76 @@ var renderWorkflowApplyReport = (report) => {
7146
7265
  `);
7147
7266
  };
7148
7267
  var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
7149
- var renderWorkflowValidationRunReport = (report) => [
7150
- `# Workflow Validation: ${report.workflow}`,
7151
- "",
7152
- `- Run: ${report.runId}`,
7153
- `- Status: ${report.status}`,
7154
- `- Source status: ${report.sourceStatus}`,
7155
- `- Workspace status: ${report.workspaceStatus}`,
7156
- `- Overall status: ${report.overallStatus}`,
7157
- "",
7158
- "## Status Summary",
7159
- `- Source-change: ${report.summary.sourceChange}`,
7160
- `- Generated sync: ${report.summary.generatedSync}`,
7161
- `- Workspace config: ${report.summary.workspaceConfig}`,
7162
- `- Environment: ${report.summary.environment}`,
7163
- "",
7164
- "## Source Change Diagnostics",
7165
- ...report.workflowDiagnostics?.length ? report.workflowDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
7166
- "",
7167
- "## Existing Workspace Blockers",
7168
- ...report.baselineDiagnostics?.length ? report.baselineDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
7169
- "",
7170
- "## Known Blockers",
7171
- ...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
7172
- const command = blocker.command ? ` \`${blocker.command}\`` : "";
7173
- const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
7174
- const known = blocker.known ? " known" : "";
7175
- return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
7176
- }) : ["- none"],
7177
- "",
7178
- "## Commands",
7179
- ...report.commands.length ? report.commands.map((command) => {
7180
- const scope = command.failureScope ? ` (${command.failureScope})` : "";
7181
- return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
7182
- }) : ["- none"],
7183
- "",
7184
- "## Diagnostics",
7185
- ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
7186
- "",
7187
- "## Repair Actions",
7188
- ...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
7189
- "",
7190
- "## Next Actions",
7191
- ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
7192
- ""
7193
- ].join(`
7268
+ var renderWorkflowValidationRunReport = (report) => {
7269
+ const baselineSummary = report.baselineSummary ?? {
7270
+ status: report.baselineDiagnostics?.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "unknown",
7271
+ total: report.baselineDiagnostics?.length ?? 0,
7272
+ totalErrors: report.baselineDiagnostics?.filter((diagnostic) => diagnostic.severity === "error").length ?? 0,
7273
+ totalWarnings: report.baselineDiagnostics?.filter((diagnostic) => diagnostic.severity === "warning").length ?? 0,
7274
+ detailsIncluded: true,
7275
+ knownBlockerCount: report.knownBlockers.filter((blocker) => blocker.known).length,
7276
+ byCode: []
7277
+ };
7278
+ return [
7279
+ `# Workflow Validation: ${report.workflow}`,
7280
+ "",
7281
+ `- Run: ${report.runId}`,
7282
+ `- Status: ${report.status}`,
7283
+ `- Source status: ${report.sourceStatus}`,
7284
+ `- Workspace status: ${report.workspaceStatus}`,
7285
+ `- Validation commands status: ${report.validationCommandsStatus ?? "unknown"}`,
7286
+ `- Baseline status: ${report.baselineStatus ?? baselineSummary.status}`,
7287
+ `- Overall status: ${report.overallStatus}`,
7288
+ "",
7289
+ "## Status Summary",
7290
+ `- Source-change: ${report.summary.sourceChange}`,
7291
+ `- Generated sync: ${report.summary.generatedSync}`,
7292
+ `- Validation commands: ${report.summary.validationCommands ?? "unknown"}`,
7293
+ `- Baseline: ${report.summary.baseline ?? baselineSummary.status}`,
7294
+ `- Workspace config: ${report.summary.workspaceConfig}`,
7295
+ `- Environment: ${report.summary.environment}`,
7296
+ "",
7297
+ "## Source Change Diagnostics",
7298
+ ...report.workflowDiagnostics?.length ? report.workflowDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
7299
+ "",
7300
+ "## Existing Workspace Blockers",
7301
+ `- Status: ${baselineSummary.status}`,
7302
+ `- Errors: ${baselineSummary.totalErrors}`,
7303
+ `- Warnings: ${baselineSummary.totalWarnings}`,
7304
+ ...baselineSummary.byCode.length ? baselineSummary.byCode.map((item) => `- [${item.severity}] ${item.code} (${item.count}x): ${item.sampleMessage}`) : ["- none"],
7305
+ ...baselineSummary.detailsIncluded ? [] : [
7306
+ "- Baseline details are summarized by default. Re-run validation with includeBaselineDetails=true for full baselineDiagnostics."
7307
+ ],
7308
+ "",
7309
+ "## Existing Workspace Blocker Details",
7310
+ ...report.baselineDiagnostics?.length ? report.baselineDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : baselineSummary.detailsIncluded ? ["- none"] : ["- omitted"],
7311
+ "",
7312
+ "## Known Blockers",
7313
+ ...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
7314
+ const command = blocker.command ? ` \`${blocker.command}\`` : "";
7315
+ const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
7316
+ const known = blocker.known ? " known" : "";
7317
+ return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
7318
+ }) : ["- none"],
7319
+ "",
7320
+ "## Commands",
7321
+ ...report.commands.length ? report.commands.map((command) => {
7322
+ const scope = command.failureScope ? ` (${command.failureScope})` : "";
7323
+ return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
7324
+ }) : ["- none"],
7325
+ "",
7326
+ "## Diagnostics",
7327
+ ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
7328
+ "",
7329
+ "## Repair Actions",
7330
+ ...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
7331
+ "",
7332
+ "## Next Actions",
7333
+ ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
7334
+ ""
7335
+ ].join(`
7194
7336
  `);
7337
+ };
7195
7338
  var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
7196
7339
  var renderRepairReportMarkdown = (report) => [
7197
7340
  `# Akan Repair: ${report.kind}`,
@@ -7280,7 +7423,11 @@ var toolingRolloutGate = {
7280
7423
  candidates: toolingRolloutCandidates
7281
7424
  };
7282
7425
  var isRestrictedCandidate = (candidate) => candidate.status !== "allowed";
7283
- var restrictedCandidates = new Map(toolingRolloutCandidates.filter(isRestrictedCandidate).map((candidate) => [candidate.packageName, candidate]));
7426
+ var restrictedCandidates = new Map;
7427
+ for (const candidate of toolingRolloutCandidates) {
7428
+ if (isRestrictedCandidate(candidate))
7429
+ restrictedCandidates.set(candidate.packageName, candidate);
7430
+ }
7284
7431
  // pkgs/@akanjs/devkit/akanContext.ts
7285
7432
  var resourceList = [
7286
7433
  { uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
@@ -7849,6 +7996,7 @@ var applyFirstPolicy = {
7849
7996
  mode: "apply-first",
7850
7997
  directSourceEdits: "fallback-only",
7851
7998
  applyRequiredWhen: ["plan_workflow returns planPath", "plan_workflow returns next.tool=apply_workflow"],
7999
+ approvalMeaning: "requiresApproval is an agent/user review signal before apply_workflow mutates files; it is not a separate MCP permission gate.",
7852
8000
  validationRequiredAfterApply: ["validationTarget", "applyReportPath"],
7853
8001
  fallbackAllowedWhen: [
7854
8002
  "list_workflows and explain_workflow show no matching workflow",
@@ -7984,7 +8132,12 @@ var readonlyMcpTools = [
7984
8132
  description: "Report workspace diagnostics, optionally split into baseline and workflow scopes.",
7985
8133
  inputSchema: {
7986
8134
  type: "object",
7987
- properties: { strict: booleanProperty, runIdOrPlan: stringProperty, changedFiles: stringArrayProperty }
8135
+ properties: {
8136
+ strict: booleanProperty,
8137
+ runIdOrPlan: stringProperty,
8138
+ changedFiles: stringArrayProperty,
8139
+ includeBaselineDetails: booleanProperty
8140
+ }
7988
8141
  }
7989
8142
  },
7990
8143
  {
@@ -8029,7 +8182,7 @@ var applyMcpTools = [
8029
8182
  description: "Validate a plan, apply report, validationTarget, or run artifact.",
8030
8183
  inputSchema: {
8031
8184
  type: "object",
8032
- properties: { runIdOrPlan: stringProperty },
8185
+ properties: { runIdOrPlan: stringProperty, includeBaselineDetails: booleanProperty },
8033
8186
  required: ["runIdOrPlan"]
8034
8187
  }
8035
8188
  },
@@ -8079,7 +8232,16 @@ var createAkanValidationContract = (listTools = listAkanMcpTools) => ({
8079
8232
  validationStatuses: {
8080
8233
  sourceStatus: ["passed", "failed", "unknown"],
8081
8234
  workspaceStatus: ["passed", "failed", "unknown"],
8082
- overallStatus: ["passed", "failed", "blocked-by-workspace-config", "blocked-by-environment"],
8235
+ validationCommandsStatus: ["passed", "failed", "unknown"],
8236
+ baselineStatus: ["passed", "failed", "unknown"],
8237
+ overallStatus: [
8238
+ "passed",
8239
+ "failed",
8240
+ "passed-with-baseline-blockers",
8241
+ "blocked-by-workspace-config",
8242
+ "blocked-by-environment"
8243
+ ],
8244
+ baselineSummary: "Default MCP validation output summarizes baseline diagnostics; pass includeBaselineDetails=true for full baselineDiagnostics.",
8083
8245
  knownBlockers: "Repeated workspace-config/environment failures are summarized before command output."
8084
8246
  },
8085
8247
  moduleContextInputs: {
@@ -8090,6 +8252,7 @@ var createAkanValidationContract = (listTools = listAkanMcpTools) => ({
8090
8252
  toolingRolloutGate,
8091
8253
  directEditFallbackPolicy: applyFirstPolicy,
8092
8254
  applyReportFields: [
8255
+ "summary",
8093
8256
  "appliedCommands",
8094
8257
  "recommendedValidationCommands",
8095
8258
  "commands",
@@ -8131,7 +8294,14 @@ var inspectAkanContext = async (workspace, args) => {
8131
8294
  strict: true,
8132
8295
  runIdOrPlan: workspacePath(workspace, props.request.runIdOrPlan)
8133
8296
  });
8134
- diagnostics.push(...doctor.diagnostics.map((diagnostic) => ({
8297
+ const baselineSummary = createWorkflowBaselineSummary((doctor.baselineDiagnostics ?? []).map((diagnostic) => ({
8298
+ severity: diagnostic.severity,
8299
+ code: diagnostic.code,
8300
+ message: diagnostic.message,
8301
+ scope: diagnostic.scope,
8302
+ context: diagnostic.context
8303
+ })), { detailsIncluded: false });
8304
+ diagnostics.push(...(doctor.workflowDiagnostics ?? doctor.diagnostics).map((diagnostic) => ({
8135
8305
  severity: diagnostic.severity,
8136
8306
  code: diagnostic.code,
8137
8307
  message: diagnostic.message,
@@ -8152,7 +8322,8 @@ var inspectAkanContext = async (workspace, args) => {
8152
8322
  reason: doctor.status === "passed" ? "No strict workspace diagnostics were found for the workflow context." : "Review diagnostics before applying or manually editing source files."
8153
8323
  }, {
8154
8324
  status: doctor.status,
8155
- baselineDiagnostics: doctor.baselineDiagnostics?.length ?? 0,
8325
+ baselineSummary,
8326
+ baselineDiagnostics: 0,
8156
8327
  workflowDiagnostics: doctor.workflowDiagnostics?.length ?? 0
8157
8328
  });
8158
8329
  }
@@ -12333,6 +12504,7 @@ void run().catch((error) => {
12333
12504
  }
12334
12505
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
12335
12506
  import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
12507
+ import path38 from "path";
12336
12508
 
12337
12509
  // pkgs/@akanjs/devkit/uploadRelease.ts
12338
12510
  import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
@@ -12448,13 +12620,9 @@ class ApplicationReleasePackager {
12448
12620
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
12449
12621
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
12450
12622
  await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12451
- await this.#app.workspace.spawn("tar", [
12452
- "-zcf",
12453
- `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-release.tar.gz`,
12454
- "-C",
12455
- buildRoot,
12456
- "./"
12457
- ]);
12623
+ const releaseRoot = this.#app.workspace.workspaceRoot;
12624
+ const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
12625
+ await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
12458
12626
  await this.#writeCsrZipIfPresent();
12459
12627
  return { platformVersion };
12460
12628
  }
@@ -12475,22 +12643,18 @@ class ApplicationReleasePackager {
12475
12643
  await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
12476
12644
  const libDeps = ["social", "shared", "platform", "util"];
12477
12645
  await Promise.all(libDeps.map((lib) => cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true })));
12478
- await Promise.all([".next", "ios", "android", "public/libs"].map(async (path38) => {
12479
- const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path38}`;
12646
+ await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
12647
+ const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
12480
12648
  if (await FileSys.dirExists(targetPath))
12481
12649
  await rm4(targetPath, { recursive: true, force: true });
12482
12650
  }));
12483
12651
  const syncPaths = [".husky", ".gitignore", "package.json"];
12484
- await Promise.all(syncPaths.map((path38) => cp(`${this.#app.workspace.cwdPath}/${path38}`, `${sourceRoot}/${path38}`, { recursive: true })));
12652
+ await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
12485
12653
  await this.#writeSourceTsconfig(sourceRoot, libDeps);
12486
12654
  await Bun.write(`${sourceRoot}/README.md`, readme);
12487
- await this.#app.workspace.spawn("tar", [
12488
- "-zcf",
12489
- `${this.#app.workspace.cwdPath}/releases/sources/${this.#app.name}-source.tar.gz`,
12490
- "-C",
12491
- sourceRoot,
12492
- "./"
12493
- ]);
12655
+ const sourceCwd = this.#app.workspace.cwdPath;
12656
+ const sourceArchivePath = path38.relative(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`).split(path38.sep).join("/");
12657
+ await this.#app.workspace.spawn("tar", ["-zcf", sourceArchivePath, "-C", sourceRoot, "./"], { cwd: sourceCwd });
12494
12658
  }
12495
12659
  async#resetSourceRoot(sourceRoot) {
12496
12660
  if (await FileSys.dirExists(sourceRoot)) {
@@ -12572,20 +12736,20 @@ class ApplicationReleasePackager {
12572
12736
  }
12573
12737
  }
12574
12738
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
12575
- import path38 from "path";
12739
+ import path39 from "path";
12576
12740
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
12577
12741
  async function resolveSignalTestPreloadPath(target) {
12578
12742
  const candidates = [];
12579
12743
  const addResolvedPackageCandidate = (basePath2) => {
12580
12744
  try {
12581
- candidates.push(path38.join(path38.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
12745
+ candidates.push(path39.join(path39.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
12582
12746
  } catch {}
12583
12747
  };
12584
12748
  addResolvedPackageCandidate(target.cwdPath);
12585
12749
  addResolvedPackageCandidate(process.cwd());
12586
- addResolvedPackageCandidate(path38.dirname(Bun.main));
12750
+ addResolvedPackageCandidate(path39.dirname(Bun.main));
12587
12751
  addResolvedPackageCandidate(import.meta.dir);
12588
- candidates.push(path38.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path38.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path38.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path38.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path38.join(path38.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path38.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
12752
+ candidates.push(path39.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(path39.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
12589
12753
  for (const candidate of [...new Set(candidates)]) {
12590
12754
  if (await Bun.file(candidate).exists())
12591
12755
  return candidate;
@@ -12595,7 +12759,7 @@ async function resolveSignalTestPreloadPath(target) {
12595
12759
  // pkgs/@akanjs/devkit/builder.ts
12596
12760
  import { existsSync as existsSync2 } from "fs";
12597
12761
  import { mkdir as mkdir10 } from "fs/promises";
12598
- import path39 from "path";
12762
+ import path40 from "path";
12599
12763
  var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
12600
12764
  var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
12601
12765
  var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
@@ -12612,14 +12776,14 @@ class Builder {
12612
12776
  #globEntrypoints(cwd, pattern) {
12613
12777
  const glob = new Bun.Glob(pattern);
12614
12778
  return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
12615
- const segments = relativePath.split(path39.sep);
12779
+ const segments = relativePath.split(path40.sep);
12616
12780
  return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
12617
- }).map((rel) => path39.join(cwd, rel));
12781
+ }).map((rel) => path40.join(cwd, rel));
12618
12782
  }
12619
12783
  #globFiles(cwd, pattern = "**/*.*") {
12620
12784
  const glob = new Bun.Glob(pattern);
12621
12785
  return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
12622
- const segments = relativePath.split(path39.sep);
12786
+ const segments = relativePath.split(path40.sep);
12623
12787
  return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
12624
12788
  });
12625
12789
  }
@@ -12627,17 +12791,17 @@ class Builder {
12627
12791
  const out = [];
12628
12792
  for (const p of additionalEntryPoints) {
12629
12793
  if (p.includes("*")) {
12630
- const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path39.sep}`) ? p.slice(cwd.length + 1) : p;
12794
+ const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path40.sep}`) ? p.slice(cwd.length + 1) : p;
12631
12795
  out.push(...this.#globEntrypoints(cwd, rel));
12632
12796
  } else
12633
- out.push(path39.isAbsolute(p) ? p : path39.join(cwd, p));
12797
+ out.push(path40.isAbsolute(p) ? p : path40.join(cwd, p));
12634
12798
  }
12635
12799
  return out;
12636
12800
  }
12637
12801
  #getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
12638
12802
  const cwd = this.#executor.cwdPath;
12639
12803
  const entrypoints = [
12640
- ...bundle ? [path39.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
12804
+ ...bundle ? [path40.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
12641
12805
  ...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
12642
12806
  ];
12643
12807
  return {
@@ -12658,9 +12822,9 @@ class Builder {
12658
12822
  for (const relativePath of this.#globFiles(cwd)) {
12659
12823
  if (relativePath === "package.json")
12660
12824
  continue;
12661
- const sourcePath = path39.join(cwd, relativePath);
12662
- const targetPath = path39.join(this.#distExecutor.cwdPath, relativePath);
12663
- await mkdir10(path39.dirname(targetPath), { recursive: true });
12825
+ const sourcePath = path40.join(cwd, relativePath);
12826
+ const targetPath = path40.join(this.#distExecutor.cwdPath, relativePath);
12827
+ await mkdir10(path40.dirname(targetPath), { recursive: true });
12664
12828
  await Bun.write(targetPath, Bun.file(sourcePath));
12665
12829
  }
12666
12830
  }
@@ -12671,13 +12835,13 @@ class Builder {
12671
12835
  return withoutFormatDir;
12672
12836
  if (!hasDotSlash && withoutFormatDir === publishedPath)
12673
12837
  return publishedPath;
12674
- const parsed = path39.posix.parse(withoutFormatDir);
12838
+ const parsed = path40.posix.parse(withoutFormatDir);
12675
12839
  if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
12676
12840
  return withoutFormatDir;
12677
- const withoutExt = path39.posix.join(parsed.dir, parsed.name);
12841
+ const withoutExt = path40.posix.join(parsed.dir, parsed.name);
12678
12842
  const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
12679
12843
  const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
12680
- const matchedSource = sourceCandidates.find((candidate) => existsSync2(path39.join(this.#executor.cwdPath, candidate)));
12844
+ const matchedSource = sourceCandidates.find((candidate) => existsSync2(path40.join(this.#executor.cwdPath, candidate)));
12681
12845
  if (!matchedSource)
12682
12846
  return withoutFormatDir;
12683
12847
  return hasDotSlash ? `./${matchedSource}` : matchedSource;
@@ -12728,7 +12892,7 @@ class Builder {
12728
12892
  // pkgs/@akanjs/devkit/capacitorApp.ts
12729
12893
  import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
12730
12894
  import os from "os";
12731
- import path40 from "path";
12895
+ import path41 from "path";
12732
12896
  import { select as select2 } from "@inquirer/prompts";
12733
12897
  import { MobileProject } from "@trapezedev/project";
12734
12898
  import { capitalize as capitalize5 } from "akanjs/common";
@@ -12935,13 +13099,13 @@ var rootCapacitorConfigFilenames = [
12935
13099
  "capacitor.config.js",
12936
13100
  "capacitor.config.json"
12937
13101
  ];
12938
- var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path40.join(appRoot, file));
13102
+ var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
12939
13103
  async function clearRootCapacitorConfigs(appRoot) {
12940
13104
  await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
12941
13105
  }
12942
13106
  async function writeRootCapacitorConfig(appRoot, content) {
12943
13107
  await clearRootCapacitorConfigs(appRoot);
12944
- await Bun.write(path40.join(appRoot, "capacitor.config.json"), content);
13108
+ await Bun.write(path41.join(appRoot, "capacitor.config.json"), content);
12945
13109
  }
12946
13110
  var getLocalIP = () => {
12947
13111
  const interfaces = os.networkInterfaces();
@@ -13050,13 +13214,13 @@ function buildIosNativeRunCommand({
13050
13214
  scheme = "App",
13051
13215
  configuration = "Debug"
13052
13216
  }) {
13053
- const derivedDataPath = path40.join(appRoot, "ios/DerivedData", device.id);
13217
+ const derivedDataPath = path41.join(appRoot, "ios/DerivedData", device.id);
13054
13218
  const productPlatform = device.kind === "device" ? "iphoneos" : "iphonesimulator";
13055
13219
  const destination = device.kind === "device" ? `id=${device.xcodebuildId ?? device.id}` : `platform=iOS Simulator,id=${device.id}`;
13056
13220
  return {
13057
13221
  configuration,
13058
13222
  derivedDataPath,
13059
- appPath: path40.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
13223
+ appPath: path41.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
13060
13224
  xcodebuildArgs: [
13061
13225
  "-project",
13062
13226
  "App.xcodeproj",
@@ -13266,7 +13430,7 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13266
13430
  ...capacitorConfig,
13267
13431
  appId,
13268
13432
  appName,
13269
- webDir: path40.posix.join(".akan", "mobile", name, "www"),
13433
+ webDir: path41.posix.join(".akan", "mobile", name, "www"),
13270
13434
  plugins: {
13271
13435
  CapacitorCookies: { enabled: true },
13272
13436
  ...pluginsConfig,
@@ -13323,10 +13487,10 @@ class CapacitorApp {
13323
13487
  constructor(app, target) {
13324
13488
  this.app = app;
13325
13489
  this.target = target;
13326
- this.targetRootPath = path40.posix.join(".akan", "mobile", this.target.name);
13327
- this.targetRoot = path40.join(this.app.cwdPath, this.targetRootPath);
13328
- this.targetWebRoot = path40.join(this.targetRoot, "www");
13329
- this.targetAssetRoot = path40.join(this.targetRoot, "assets");
13490
+ this.targetRootPath = path41.posix.join(".akan", "mobile", this.target.name);
13491
+ this.targetRoot = path41.join(this.app.cwdPath, this.targetRootPath);
13492
+ this.targetWebRoot = path41.join(this.targetRoot, "www");
13493
+ this.targetAssetRoot = path41.join(this.targetRoot, "assets");
13330
13494
  this.project = new MobileProject(this.app.cwdPath, {
13331
13495
  android: { path: this.androidRootPath },
13332
13496
  ios: { path: this.iosProjectPath }
@@ -13341,9 +13505,9 @@ class CapacitorApp {
13341
13505
  await mkdir11(this.targetRoot, { recursive: true });
13342
13506
  if (regenerate) {
13343
13507
  if (!platform || platform === "ios")
13344
- await rm5(path40.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13508
+ await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13345
13509
  if (!platform || platform === "android")
13346
- await rm5(path40.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13510
+ await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13347
13511
  }
13348
13512
  const project = this.project;
13349
13513
  await this.project.load();
@@ -13466,7 +13630,7 @@ ${textError instanceof Error ? textError.message : ""}`);
13466
13630
  const xcodebuildArgs = noAllowProvisioningUpdates ? command.xcodebuildArgs : [...command.xcodebuildArgs.slice(0, -1), "-allowProvisioningUpdates", ...command.xcodebuildArgs.slice(-1)];
13467
13631
  try {
13468
13632
  await this.#spawn("xcodebuild", xcodebuildArgs, {
13469
- cwd: path40.join(this.app.cwdPath, this.iosProjectPath),
13633
+ cwd: path41.join(this.app.cwdPath, this.iosProjectPath),
13470
13634
  env: mobileEnv
13471
13635
  });
13472
13636
  const devicectlId = runTarget.devicectlId ?? runTarget.id;
@@ -13491,7 +13655,7 @@ ${textError instanceof Error ? textError.message : ""}`);
13491
13655
  return isRecord2(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
13492
13656
  }
13493
13657
  async#getIosDevelopmentTeam() {
13494
- const pbxprojPath = path40.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
13658
+ const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
13495
13659
  if (!await Bun.file(pbxprojPath).exists())
13496
13660
  return;
13497
13661
  return (await Bun.file(pbxprojPath).text()).match(/DEVELOPMENT_TEAM = ([^;]+);/)?.[1]?.trim();
@@ -13517,7 +13681,7 @@ ${error.message}`;
13517
13681
  await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
13518
13682
  }
13519
13683
  async#updateAndroidBuildTypes() {
13520
- const appGradle = await FileEditor.create(path40.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
13684
+ const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
13521
13685
  const buildTypesBlock = `
13522
13686
  debug {
13523
13687
  applicationIdSuffix ".debug"
@@ -13561,7 +13725,7 @@ ${error.message}`;
13561
13725
  const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
13562
13726
  await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
13563
13727
  stdio: "inherit",
13564
- cwd: path40.join(this.app.cwdPath, this.androidRootPath),
13728
+ cwd: path41.join(this.app.cwdPath, this.androidRootPath),
13565
13729
  env: await this.#commandEnv("release", env)
13566
13730
  });
13567
13731
  }
@@ -13569,10 +13733,10 @@ ${error.message}`;
13569
13733
  await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
13570
13734
  }
13571
13735
  async#ensureAndroidAssetsDir() {
13572
- await mkdir11(path40.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
13736
+ await mkdir11(path41.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
13573
13737
  }
13574
13738
  async#ensureAndroidDebugKeystore() {
13575
- const keystorePath = path40.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
13739
+ const keystorePath = path41.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
13576
13740
  if (await Bun.file(keystorePath).exists())
13577
13741
  return;
13578
13742
  await this.#spawn("keytool", [
@@ -13611,7 +13775,7 @@ ${error.message}`;
13611
13775
  await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
13612
13776
  }
13613
13777
  async#assertAndroidReleaseSigningConfig() {
13614
- const gradlePropertiesPath = path40.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
13778
+ const gradlePropertiesPath = path41.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
13615
13779
  const gradleProperties = await Bun.file(gradlePropertiesPath).exists() ? await Bun.file(gradlePropertiesPath).text() : "";
13616
13780
  const missingKeys = getMissingAndroidReleaseSigningKeys({ gradleProperties });
13617
13781
  if (missingKeys.length > 0)
@@ -13638,12 +13802,12 @@ ${error.message}`;
13638
13802
  await this.#prepareAndroid({ operation: "release", env: "main" });
13639
13803
  }
13640
13804
  async prepareWww() {
13641
- const htmlSource = path40.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
13805
+ const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
13642
13806
  if (!await Bun.file(htmlSource).exists())
13643
13807
  throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
13644
13808
  await rm5(this.targetWebRoot, { recursive: true, force: true });
13645
13809
  await mkdir11(this.targetWebRoot, { recursive: true });
13646
- await Bun.write(path40.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
13810
+ await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
13647
13811
  }
13648
13812
  #injectMobileTargetMeta(html) {
13649
13813
  const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
@@ -13663,7 +13827,7 @@ ${error.message}`;
13663
13827
  });
13664
13828
  const content = `${JSON.stringify(config, null, 2)}
13665
13829
  `;
13666
- await Bun.write(path40.join(this.targetRoot, "capacitor.config.json"), content);
13830
+ await Bun.write(path41.join(this.targetRoot, "capacitor.config.json"), content);
13667
13831
  return content;
13668
13832
  }
13669
13833
  async#prepareTargetAssets() {
@@ -13671,11 +13835,11 @@ ${error.message}`;
13671
13835
  return;
13672
13836
  await mkdir11(this.targetAssetRoot, { recursive: true });
13673
13837
  if (this.target.assets.icon)
13674
- await cp2(path40.join(this.app.cwdPath, this.target.assets.icon), path40.join(this.targetAssetRoot, "icon.png"), {
13838
+ await cp2(path41.join(this.app.cwdPath, this.target.assets.icon), path41.join(this.targetAssetRoot, "icon.png"), {
13675
13839
  force: true
13676
13840
  });
13677
13841
  if (this.target.assets.splash)
13678
- await cp2(path40.join(this.app.cwdPath, this.target.assets.splash), path40.join(this.targetAssetRoot, "splash.png"), {
13842
+ await cp2(path41.join(this.app.cwdPath, this.target.assets.splash), path41.join(this.targetAssetRoot, "splash.png"), {
13679
13843
  force: true
13680
13844
  });
13681
13845
  }
@@ -13683,11 +13847,11 @@ ${error.message}`;
13683
13847
  const files = this.target.files?.[platform];
13684
13848
  if (!files)
13685
13849
  return;
13686
- const platformRoot = path40.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
13850
+ const platformRoot = path41.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
13687
13851
  await Promise.all(Object.entries(files).map(async ([to, from]) => {
13688
- const targetPath = path40.join(platformRoot, to);
13689
- await mkdir11(path40.dirname(targetPath), { recursive: true });
13690
- await cp2(path40.join(this.app.cwdPath, from), targetPath, { force: true });
13852
+ const targetPath = path41.join(platformRoot, to);
13853
+ await mkdir11(path41.dirname(targetPath), { recursive: true });
13854
+ await cp2(path41.join(this.app.cwdPath, from), targetPath, { force: true });
13691
13855
  }));
13692
13856
  }
13693
13857
  async#generateAssets({ operation, env }) {
@@ -13697,7 +13861,7 @@ ${error.message}`;
13697
13861
  "@capacitor/assets",
13698
13862
  "generate",
13699
13863
  "--assetPath",
13700
- path40.posix.join(this.targetRootPath, "assets"),
13864
+ path41.posix.join(this.targetRootPath, "assets"),
13701
13865
  "--iosProject",
13702
13866
  this.iosProjectPath,
13703
13867
  "--androidProject",
@@ -13907,7 +14071,7 @@ var Pkg = createInternalArgToken("Pkg");
13907
14071
  var Module = createInternalArgToken("Module");
13908
14072
  var Workspace = createInternalArgToken("Workspace");
13909
14073
  // pkgs/@akanjs/devkit/commandDecorators/command.ts
13910
- import path41 from "path";
14074
+ import path42 from "path";
13911
14075
  import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
13912
14076
  import { Logger as Logger10 } from "akanjs/common";
13913
14077
  import chalk6 from "chalk";
@@ -14411,7 +14575,7 @@ var runCommands = async (...commands) => {
14411
14575
  process.exit(1);
14412
14576
  });
14413
14577
  const __dirname2 = getDirname(import.meta.url);
14414
- const packageJsonCandidates = [`${path41.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
14578
+ const packageJsonCandidates = [`${path42.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
14415
14579
  let cliPackageJson = null;
14416
14580
  for (const packageJsonPath of packageJsonCandidates) {
14417
14581
  if (!await FileSys.fileExists(packageJsonPath))
@@ -14691,8 +14855,8 @@ var scanModuleSpecifiers = (source2, filePath, includeExports) => {
14691
14855
  return importSpecifiers;
14692
14856
  };
14693
14857
  var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
14694
- const configFile = ts10.readConfigFile(tsConfigPath, (path42) => {
14695
- return ts10.sys.readFile(path42);
14858
+ const configFile = ts10.readConfigFile(tsConfigPath, (path43) => {
14859
+ return ts10.sys.readFile(path43);
14696
14860
  });
14697
14861
  return ts10.parseJsonConfigFileContent(configFile.config, ts10.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
14698
14862
  };
@@ -14908,6 +15072,571 @@ ${document}
14908
15072
  `;
14909
15073
  }
14910
15074
  }
15075
+ // pkgs/@akanjs/devkit/qualityScanner.ts
15076
+ import { createHash } from "crypto";
15077
+ import { readdir as readdir3, readFile as readFile2, stat as stat4 } from "fs/promises";
15078
+ import path43 from "path";
15079
+ import ignore2 from "ignore";
15080
+ import ts11 from "typescript";
15081
+ var MAX_FILE_LINES = 2000;
15082
+ var PLACEHOLDER_EXPORT_NAMES = new Set([
15083
+ "aa",
15084
+ "dumb",
15085
+ "dumb2",
15086
+ "someBaseLogic",
15087
+ "someCommonLogic",
15088
+ "someFrontendLogic",
15089
+ "someBackendLogic"
15090
+ ]);
15091
+ var SUGGESTED_RULES = [
15092
+ "Keep generated scanSync index files out of hand-written changes; generated indexes should only contain one-depth export statements.",
15093
+ "Generated Akan index files should not contain placeholder exports such as aa, dumb, dumb2, or some*Logic.",
15094
+ "Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
15095
+ "Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
15096
+ "Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
15097
+ "Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
15098
+ "Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
15099
+ "Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
15100
+ "Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
15101
+ "Move shared app utilities to apps/*/common instead of creating apps/*/base.",
15102
+ "Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
15103
+ ];
15104
+ var APP_ROOT_FILES = new Set([
15105
+ "akan.app.json",
15106
+ "akan.config.ts",
15107
+ "capacitor.config.ts",
15108
+ "client.ts",
15109
+ "main.ts",
15110
+ "package.json",
15111
+ "server.ts",
15112
+ "tsconfig.json"
15113
+ ]);
15114
+ var LIB_ROOT_FILES = new Set([
15115
+ "cnst.ts",
15116
+ "db.ts",
15117
+ "dict.ts",
15118
+ "option.ts",
15119
+ "sig.ts",
15120
+ "srv.ts",
15121
+ "st.ts",
15122
+ "useClient.ts",
15123
+ "useServer.ts"
15124
+ ]);
15125
+ var CONVENTION_SUFFIXES = [
15126
+ ".constant.ts",
15127
+ ".dictionary.ts",
15128
+ ".document.ts",
15129
+ ".service.ts",
15130
+ ".signal.ts",
15131
+ ".store.ts"
15132
+ ];
15133
+
15134
+ class AkanQualityScanner {
15135
+ async scan(workspaceRoot) {
15136
+ const targetFiles = await this.#collectTargetFiles(workspaceRoot);
15137
+ const sourceFiles = await Promise.all(targetFiles.map((file) => this.#readSourceFile(workspaceRoot, file)));
15138
+ const warnings = [
15139
+ ...this.#scanGlobalQuality(sourceFiles),
15140
+ ...sourceFiles.flatMap((sourceFile2) => this.#scanSingleFileQuality(sourceFile2)),
15141
+ ...sourceFiles.flatMap((sourceFile2) => this.#scanConventionQuality(sourceFile2)),
15142
+ ...sourceFiles.flatMap((sourceFile2) => this.#scanLayoutQuality(sourceFile2))
15143
+ ];
15144
+ return {
15145
+ workspaceRoot,
15146
+ scannedFiles: sourceFiles.length,
15147
+ warnings: warnings.sort(compareWarnings),
15148
+ suggestedRules: SUGGESTED_RULES
15149
+ };
15150
+ }
15151
+ async#collectTargetFiles(workspaceRoot) {
15152
+ const ignoreFilter = ignore2().add(await this.#readGitIgnore(workspaceRoot));
15153
+ const files = [];
15154
+ for (const targetRoot of ["apps", "libs"]) {
15155
+ const absoluteTargetRoot = path43.join(workspaceRoot, targetRoot);
15156
+ if (!await isDirectory(absoluteTargetRoot))
15157
+ continue;
15158
+ await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
15159
+ }
15160
+ return files.sort();
15161
+ }
15162
+ async#readGitIgnore(workspaceRoot) {
15163
+ const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
15164
+ if (!await Bun.file(gitIgnorePath).exists())
15165
+ return [];
15166
+ return (await readFile2(gitIgnorePath, "utf8")).split(/\r?\n/);
15167
+ }
15168
+ async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
15169
+ const entries = await readdir3(currentPath, { withFileTypes: true });
15170
+ for (const entry of entries) {
15171
+ const absolutePath = path43.join(currentPath, entry.name);
15172
+ const relativePath = toPosix(path43.relative(workspaceRoot, absolutePath));
15173
+ if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
15174
+ continue;
15175
+ if (entry.isDirectory()) {
15176
+ await this.#walkTargetFiles(workspaceRoot, absolutePath, ignoreFilter, files);
15177
+ continue;
15178
+ }
15179
+ if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
15180
+ files.push(relativePath);
15181
+ }
15182
+ }
15183
+ }
15184
+ async#readSourceFile(workspaceRoot, file) {
15185
+ const absolutePath = path43.join(workspaceRoot, file);
15186
+ const content = await readFile2(absolutePath, "utf8");
15187
+ return {
15188
+ file,
15189
+ absolutePath,
15190
+ content,
15191
+ sourceFile: ts11.createSourceFile(file, content, ts11.ScriptTarget.Latest, true, getScriptKind(file))
15192
+ };
15193
+ }
15194
+ #scanGlobalQuality(sourceFiles) {
15195
+ const exportedFunctionLikes = sourceFiles.flatMap((sourceFile2) => getExportedFunctionLikes(sourceFile2));
15196
+ const warnings = [];
15197
+ for (const [name, declarations] of groupBy(exportedFunctionLikes, (declaration) => declaration.name)) {
15198
+ if (declarations.length < 2)
15199
+ continue;
15200
+ warnings.push({
15201
+ rule: "akan.global.duplicate-exported-function-name",
15202
+ scope: "global",
15203
+ severity: "warning",
15204
+ message: `Exported function or class name "${name}" is declared in ${declarations.length} files.`,
15205
+ locations: declarations.map(({ file, line }) => ({ file, line }))
15206
+ });
15207
+ }
15208
+ const declarationsWithBody = exportedFunctionLikes.filter((declaration) => declaration.bodyFingerprint);
15209
+ for (const [fingerprint, declarations] of groupBy(declarationsWithBody, (declaration) => declaration.bodyFingerprint ?? "")) {
15210
+ const uniqueNames = new Set(declarations.map((declaration) => declaration.name));
15211
+ if (declarations.length < 2 || uniqueNames.size < 2 || fingerprint === "")
15212
+ continue;
15213
+ warnings.push({
15214
+ rule: "akan.global.duplicate-exported-function-body",
15215
+ scope: "global",
15216
+ severity: "warning",
15217
+ message: `Exported functions/classes share the same implementation body: ${declarations.map((declaration) => declaration.name).join(", ")}.`,
15218
+ locations: declarations.map(({ file, line }) => ({ file, line }))
15219
+ });
15220
+ }
15221
+ return warnings;
15222
+ }
15223
+ #scanSingleFileQuality(sourceFile2) {
15224
+ const warnings = [];
15225
+ const lineCount = sourceFile2.content.split(/\r?\n/).length;
15226
+ const recommendedLineLimit = getRecommendedLineLimit(sourceFile2.file);
15227
+ if (recommendedLineLimit && lineCount > recommendedLineLimit) {
15228
+ warnings.push({
15229
+ rule: "akan.file.recommended-max-lines",
15230
+ scope: "file",
15231
+ severity: "warning",
15232
+ file: sourceFile2.file,
15233
+ message: `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`
15234
+ });
15235
+ }
15236
+ if (lineCount > MAX_FILE_LINES) {
15237
+ warnings.push({
15238
+ rule: "akan.file.max-lines",
15239
+ scope: "file",
15240
+ severity: "warning",
15241
+ file: sourceFile2.file,
15242
+ message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
15243
+ });
15244
+ }
15245
+ warnings.push(...getPlaceholderExportWarnings(sourceFile2));
15246
+ warnings.push(...getDictionaryTextWarnings(sourceFile2));
15247
+ warnings.push(...getGlobalMutationWarnings(sourceFile2));
15248
+ const exportedClassNames = getExportedClassNames(sourceFile2.sourceFile);
15249
+ if (exportedClassNames.length === 0)
15250
+ return warnings;
15251
+ const allowedInterfaceNames = new Set(exportedClassNames.map((name) => `${name}Options`));
15252
+ for (const declaration of getTopLevelDeclarations(sourceFile2)) {
15253
+ if (declaration.kind === "class" && exportedClassNames.includes(declaration.name))
15254
+ continue;
15255
+ if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name))
15256
+ continue;
15257
+ warnings.push({
15258
+ rule: "akan.file.class-export-global-declaration",
15259
+ scope: "file",
15260
+ severity: "warning",
15261
+ file: sourceFile2.file,
15262
+ line: declaration.line,
15263
+ message: `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`
15264
+ });
15265
+ }
15266
+ return warnings;
15267
+ }
15268
+ #scanConventionQuality(sourceFile2) {
15269
+ const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile2.file.endsWith(candidate));
15270
+ if (!suffix)
15271
+ return [];
15272
+ const modelName = toPascalCase(path43.basename(sourceFile2.file, suffix));
15273
+ const warnings = [];
15274
+ for (const declaration of getTopLevelDeclarations(sourceFile2)) {
15275
+ if (isAllowedConventionDeclaration(suffix, modelName, declaration))
15276
+ continue;
15277
+ warnings.push({
15278
+ rule: `akan.convention${suffix.replace(".ts", "")}`,
15279
+ scope: "convention",
15280
+ severity: "warning",
15281
+ file: sourceFile2.file,
15282
+ line: declaration.line,
15283
+ message: `${path43.basename(sourceFile2.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
15284
+ });
15285
+ }
15286
+ return warnings;
15287
+ }
15288
+ #scanLayoutQuality(sourceFile2) {
15289
+ const segments = sourceFile2.file.split("/");
15290
+ const warnings = [];
15291
+ if (segments[0] === "apps" && segments.length === 3 && !APP_ROOT_FILES.has(segments[2])) {
15292
+ warnings.push({
15293
+ rule: "akan.layout.app-root-file",
15294
+ scope: "layout",
15295
+ severity: "warning",
15296
+ file: sourceFile2.file,
15297
+ message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
15298
+ });
15299
+ }
15300
+ const libRootFile = getLibRootFile(sourceFile2.file);
15301
+ if (libRootFile && !LIB_ROOT_FILES.has(libRootFile)) {
15302
+ warnings.push({
15303
+ rule: "akan.layout.lib-root-file",
15304
+ scope: "layout",
15305
+ severity: "warning",
15306
+ file: sourceFile2.file,
15307
+ message: `Unexpected lib root file "${libRootFile}". Keep direct lib root files limited to generated support facets.`
15308
+ });
15309
+ }
15310
+ const moduleUiWarning = getModuleUiWarning(sourceFile2.file);
15311
+ if (moduleUiWarning)
15312
+ warnings.push(moduleUiWarning);
15313
+ return warnings;
15314
+ }
15315
+ }
15316
+ function formatQualityScanResult(result) {
15317
+ const sections = [
15318
+ "Akan Code Quality Scan",
15319
+ `workspace: ${result.workspaceRoot}`,
15320
+ `scanned files: ${result.scannedFiles}`,
15321
+ `warnings: ${result.warnings.length}`,
15322
+ "",
15323
+ "Warnings:",
15324
+ "",
15325
+ ...formatQualityWarnings(result.warnings),
15326
+ "",
15327
+ "Suggested quality rules:",
15328
+ "",
15329
+ ...result.suggestedRules.map((rule) => ` - ${rule}`)
15330
+ ];
15331
+ return sections.join(`
15332
+ `);
15333
+ }
15334
+ function formatQualityWarnings(warnings) {
15335
+ if (warnings.length === 0)
15336
+ return ["No warnings found."];
15337
+ return warnings.flatMap((warning) => {
15338
+ const location = formatQualityLocation(warning.file, warning.line);
15339
+ const lines = [`${location} - warning ${warning.rule}: ${warning.message}`];
15340
+ if (warning.locations?.length) {
15341
+ lines.push(...warning.locations.map(({ file, line }) => ` note: related location ${formatQualityLocation(file, line)}`));
15342
+ }
15343
+ return lines;
15344
+ });
15345
+ }
15346
+ function formatQualityLocation(file, line) {
15347
+ return `${file ?? "<global>"}:${line ?? 1}:1`;
15348
+ }
15349
+ function getExportedFunctionLikes(sourceFile2) {
15350
+ const declarations = [];
15351
+ for (const statement of sourceFile2.sourceFile.statements) {
15352
+ if (ts11.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
15353
+ declarations.push({
15354
+ name: statement.name.text,
15355
+ kind: "function",
15356
+ file: sourceFile2.file,
15357
+ line: getLine(sourceFile2.sourceFile, statement),
15358
+ bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement.body)
15359
+ });
15360
+ }
15361
+ if (ts11.isClassDeclaration(statement) && statement.name && isExported(statement)) {
15362
+ declarations.push({
15363
+ name: statement.name.text,
15364
+ kind: "class",
15365
+ file: sourceFile2.file,
15366
+ line: getLine(sourceFile2.sourceFile, statement),
15367
+ bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement)
15368
+ });
15369
+ }
15370
+ if (ts11.isVariableStatement(statement) && isExported(statement)) {
15371
+ for (const declaration of statement.declarationList.declarations) {
15372
+ if (!ts11.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
15373
+ continue;
15374
+ declarations.push({
15375
+ name: declaration.name.text,
15376
+ kind: "function-variable",
15377
+ file: sourceFile2.file,
15378
+ line: getLine(sourceFile2.sourceFile, declaration),
15379
+ bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, declaration.initializer)
15380
+ });
15381
+ }
15382
+ }
15383
+ }
15384
+ return declarations;
15385
+ }
15386
+ function getExportedClassNames(sourceFile2) {
15387
+ return sourceFile2.statements.filter((statement) => ts11.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
15388
+ }
15389
+ function getPlaceholderExportWarnings(sourceFile2) {
15390
+ if (!sourceFile2.file.endsWith("/index.ts") && !sourceFile2.file.endsWith("/index.tsx"))
15391
+ return [];
15392
+ return getTopLevelDeclarations(sourceFile2).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => ({
15393
+ rule: "akan.file.placeholder-export",
15394
+ scope: "file",
15395
+ severity: "warning",
15396
+ file: sourceFile2.file,
15397
+ line: declaration.line,
15398
+ message: `Generated or barrel index file should not export placeholder "${declaration.name}".`
15399
+ }));
15400
+ }
15401
+ function getDictionaryTextWarnings(sourceFile2) {
15402
+ if (!sourceFile2.file.endsWith(".dictionary.ts"))
15403
+ return [];
15404
+ const stalePatterns = [
15405
+ { pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" },
15406
+ { pattern: /settting/, label: "misspelling: settting" },
15407
+ { pattern: /\uBC30\uB108 \uC218/, label: "stale copied Korean domain noun: \uBC30\uB108 \uC218" }
15408
+ ];
15409
+ return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile2.content, pattern).map((line) => ({
15410
+ rule: "akan.file.dictionary-stale-text",
15411
+ scope: "file",
15412
+ severity: "warning",
15413
+ file: sourceFile2.file,
15414
+ line,
15415
+ message: `Dictionary text appears to contain ${label}.`
15416
+ })));
15417
+ }
15418
+ function getGlobalMutationWarnings(sourceFile2) {
15419
+ const warnings = [];
15420
+ for (const statement of sourceFile2.sourceFile.statements) {
15421
+ if (ts11.isModuleDeclaration(statement) && statement.name.getText(sourceFile2.sourceFile) === "global") {
15422
+ warnings.push({
15423
+ rule: "akan.file.global-declaration",
15424
+ scope: "file",
15425
+ severity: "warning",
15426
+ file: sourceFile2.file,
15427
+ line: getLine(sourceFile2.sourceFile, statement),
15428
+ message: "Global declarations require an explicit low-level integration allowlist."
15429
+ });
15430
+ }
15431
+ if (ts11.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
15432
+ warnings.push({
15433
+ rule: "akan.file.window-augmentation",
15434
+ scope: "file",
15435
+ severity: "warning",
15436
+ file: sourceFile2.file,
15437
+ line: getLine(sourceFile2.sourceFile, statement),
15438
+ message: "Window augmentation should be isolated to approved browser integration files."
15439
+ });
15440
+ }
15441
+ if (ts11.isExpressionStatement(statement) && statement.expression.getText(sourceFile2.sourceFile).includes(".prototype.")) {
15442
+ warnings.push({
15443
+ rule: "akan.file.prototype-mutation",
15444
+ scope: "file",
15445
+ severity: "warning",
15446
+ file: sourceFile2.file,
15447
+ line: getLine(sourceFile2.sourceFile, statement),
15448
+ message: "Prototype mutation should be avoided or isolated to approved low-level integration files."
15449
+ });
15450
+ }
15451
+ }
15452
+ return warnings;
15453
+ }
15454
+ function getTopLevelDeclarations(sourceFile2) {
15455
+ return sourceFile2.sourceFile.statements.flatMap((statement) => getTopLevelDeclaration(sourceFile2.sourceFile, statement));
15456
+ }
15457
+ function getTopLevelDeclaration(sourceFile2, statement) {
15458
+ const line = getLine(sourceFile2, statement);
15459
+ if (ts11.isClassDeclaration(statement) && statement.name) {
15460
+ return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
15461
+ }
15462
+ if (ts11.isFunctionDeclaration(statement) && statement.name) {
15463
+ return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
15464
+ }
15465
+ if (ts11.isInterfaceDeclaration(statement)) {
15466
+ return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
15467
+ }
15468
+ if (ts11.isTypeAliasDeclaration(statement)) {
15469
+ return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
15470
+ }
15471
+ if (ts11.isEnumDeclaration(statement)) {
15472
+ return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
15473
+ }
15474
+ if (ts11.isVariableStatement(statement)) {
15475
+ return statement.declarationList.declarations.filter((declaration) => ts11.isIdentifier(declaration.name)).map((declaration) => ({
15476
+ name: declaration.name.text,
15477
+ kind: "variable",
15478
+ line: getLine(sourceFile2, declaration),
15479
+ exported: isExported(statement),
15480
+ node: statement
15481
+ }));
15482
+ }
15483
+ if (ts11.isExportDeclaration(statement)) {
15484
+ return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
15485
+ }
15486
+ return [];
15487
+ }
15488
+ function isAllowedConventionDeclaration(suffix, modelName, declaration) {
15489
+ if (suffix === ".dictionary.ts")
15490
+ return isExportedConst(declaration) && declaration.name === "dictionary";
15491
+ if (suffix === ".constant.ts")
15492
+ return isAllowedConstantDeclaration(modelName, declaration);
15493
+ if (suffix === ".document.ts")
15494
+ return declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name);
15495
+ if (suffix === ".service.ts")
15496
+ return declaration.kind === "class" && declaration.name === `${modelName}Service`;
15497
+ if (suffix === ".signal.ts")
15498
+ return declaration.kind === "class" && [`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name);
15499
+ if (suffix === ".store.ts")
15500
+ return declaration.kind === "class" && declaration.name === `${modelName}Store`;
15501
+ return false;
15502
+ }
15503
+ function isAllowedConstantDeclaration(modelName, declaration) {
15504
+ if (declaration.kind !== "class")
15505
+ return false;
15506
+ if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
15507
+ return true;
15508
+ if (!ts11.isClassDeclaration(declaration.node))
15509
+ return false;
15510
+ const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts11.SyntaxKind.ExtendsKeyword);
15511
+ const expression = heritageClause?.types[0]?.expression;
15512
+ return !!expression && expression.getText().startsWith("enumOf(");
15513
+ }
15514
+ function getConventionDescription(suffix, modelName) {
15515
+ if (suffix === ".dictionary.ts")
15516
+ return "export const dictionary";
15517
+ if (suffix === ".constant.ts")
15518
+ return `${modelName}Input, ${modelName}Object, ${modelName}, Light${modelName}, ${modelName}Insight, or enumOf classes`;
15519
+ if (suffix === ".document.ts")
15520
+ return `${modelName}Filter, ${modelName}, or ${modelName}Model`;
15521
+ if (suffix === ".service.ts")
15522
+ return `${modelName}Service`;
15523
+ if (suffix === ".signal.ts")
15524
+ return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
15525
+ return `${modelName}Store`;
15526
+ }
15527
+ function getLibRootFile(file) {
15528
+ const segments = file.split("/");
15529
+ if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
15530
+ return segments[3];
15531
+ if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
15532
+ return segments[4];
15533
+ return null;
15534
+ }
15535
+ function getModuleUiWarning(file) {
15536
+ if (!file.endsWith(".tsx"))
15537
+ return null;
15538
+ const moduleInfo = getModuleInfo(file);
15539
+ if (!moduleInfo)
15540
+ return null;
15541
+ const { moduleName, fileName, kind } = moduleInfo;
15542
+ const pascalName = toPascalCase(moduleName.replace(/^_+/, ""));
15543
+ const allowedSuffixes = kind === "database" ? ["Template", "Unit", "Util", "View", "Zone"] : kind === "service" ? ["Util", "Zone"] : ["Template", "Unit"];
15544
+ const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
15545
+ if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx"))
15546
+ return null;
15547
+ return {
15548
+ rule: "akan.layout.module-ui-file",
15549
+ scope: "layout",
15550
+ severity: "warning",
15551
+ file,
15552
+ message: `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`
15553
+ };
15554
+ }
15555
+ function getModuleInfo(file) {
15556
+ const segments = file.split("/");
15557
+ const libIndex = segments.indexOf("lib");
15558
+ if (libIndex < 0)
15559
+ return null;
15560
+ const moduleName = segments[libIndex + 1];
15561
+ if (moduleName === "__scalar") {
15562
+ if (segments.length !== libIndex + 4)
15563
+ return null;
15564
+ return { moduleName: segments[libIndex + 2], fileName: segments[libIndex + 3], kind: "scalar" };
15565
+ }
15566
+ if (segments.length !== libIndex + 3)
15567
+ return null;
15568
+ const fileName = segments[libIndex + 2];
15569
+ if (moduleName.startsWith("__")) {
15570
+ const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
15571
+ return { moduleName: scalarName, fileName, kind: "scalar" };
15572
+ }
15573
+ if (moduleName.startsWith("_"))
15574
+ return { moduleName, fileName, kind: "service" };
15575
+ return { moduleName, fileName, kind: "database" };
15576
+ }
15577
+ function isExportedConst(declaration) {
15578
+ return declaration.exported && ts11.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts11.NodeFlags.Const) !== 0;
15579
+ }
15580
+ function isExported(node) {
15581
+ return !!ts11.getCombinedModifierFlags(node) && !!(ts11.getCombinedModifierFlags(node) & ts11.ModifierFlags.Export);
15582
+ }
15583
+ function isFunctionLikeInitializer(node) {
15584
+ return !!node && (ts11.isArrowFunction(node) || ts11.isFunctionExpression(node));
15585
+ }
15586
+ function getBodyFingerprint(sourceFile2, node) {
15587
+ if (!node)
15588
+ return;
15589
+ const normalizedBody = node.getText(sourceFile2).replace(/\s+/g, " ").trim();
15590
+ if (normalizedBody.length < 80)
15591
+ return;
15592
+ return createHash("sha256").update(normalizedBody).digest("hex");
15593
+ }
15594
+ function shouldSkipPath(relativePath, isDirectory, ignoreFilter) {
15595
+ const ignorePath = isDirectory ? `${relativePath}/` : relativePath;
15596
+ return relativePath === ".git" || relativePath.includes("/.git/") || relativePath.includes("/node_modules/") || ignoreFilter.ignores(relativePath) || ignoreFilter.ignores(ignorePath);
15597
+ }
15598
+ async function isDirectory(absolutePath) {
15599
+ try {
15600
+ return (await stat4(absolutePath)).isDirectory();
15601
+ } catch {
15602
+ return false;
15603
+ }
15604
+ }
15605
+ function getScriptKind(file) {
15606
+ return file.endsWith(".tsx") ? ts11.ScriptKind.TSX : ts11.ScriptKind.TS;
15607
+ }
15608
+ function getLine(sourceFile2, node) {
15609
+ return sourceFile2.getLineAndCharacterOfPosition(node.getStart(sourceFile2)).line + 1;
15610
+ }
15611
+ function getRecommendedLineLimit(file) {
15612
+ if (file.endsWith(".service.ts"))
15613
+ return 500;
15614
+ if (file.endsWith(".Template.tsx") || file.endsWith(".Zone.tsx"))
15615
+ return 800;
15616
+ if (file.endsWith(".Util.tsx"))
15617
+ return 1000;
15618
+ return null;
15619
+ }
15620
+ function findPatternLines(content, pattern) {
15621
+ return content.split(/\r?\n/).flatMap((line, index) => pattern.test(line) ? [index + 1] : []);
15622
+ }
15623
+ function toPascalCase(value) {
15624
+ return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
15625
+ }
15626
+ function toPosix(value) {
15627
+ return value.split(path43.sep).join("/");
15628
+ }
15629
+ function groupBy(items, getKey) {
15630
+ const grouped = new Map;
15631
+ for (const item of items) {
15632
+ const key = getKey(item);
15633
+ grouped.set(key, [...grouped.get(key) ?? [], item]);
15634
+ }
15635
+ return grouped;
15636
+ }
15637
+ function compareWarnings(a, b) {
15638
+ return a.scope.localeCompare(b.scope) || (a.file ?? "").localeCompare(b.file ?? "") || (a.line ?? 0) - (b.line ?? 0) || a.rule.localeCompare(b.rule);
15639
+ }
14911
15640
  // pkgs/@akanjs/devkit/selectModel.ts
14912
15641
  import { select as select5 } from "@inquirer/prompts";
14913
15642
  // pkgs/@akanjs/devkit/streamAi.ts
@@ -15025,10 +15754,10 @@ class IncrementalBuilder {
15025
15754
  }
15026
15755
  }
15027
15756
  batchTouchesPagesTree(appDir, batch) {
15028
- const absAppDir = path42.resolve(appDir);
15757
+ const absAppDir = path44.resolve(appDir);
15029
15758
  for (const f of batch.files) {
15030
- const abs = path42.resolve(f);
15031
- if (!abs.startsWith(`${absAppDir}${path42.sep}`) && abs !== absAppDir)
15759
+ const abs = path44.resolve(f);
15760
+ if (!abs.startsWith(`${absAppDir}${path44.sep}`) && abs !== absAppDir)
15032
15761
  continue;
15033
15762
  if (/\.(tsx|ts|jsx|js)$/.test(abs))
15034
15763
  return true;
@@ -15036,15 +15765,15 @@ class IncrementalBuilder {
15036
15765
  return false;
15037
15766
  }
15038
15767
  async batchMayChangePageKeys(appDir, batch) {
15039
- const absAppDir = path42.resolve(appDir);
15040
- const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path42.normalize(key)));
15768
+ const absAppDir = path44.resolve(appDir);
15769
+ const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path44.normalize(key)));
15041
15770
  for (const f of batch.files) {
15042
- const abs = path42.resolve(f);
15043
- if (!abs.startsWith(`${absAppDir}${path42.sep}`) && abs !== absAppDir)
15771
+ const abs = path44.resolve(f);
15772
+ if (!abs.startsWith(`${absAppDir}${path44.sep}`) && abs !== absAppDir)
15044
15773
  continue;
15045
15774
  if (!/\.(tsx|ts|jsx|js)$/.test(abs))
15046
15775
  continue;
15047
- const rel = path42.normalize(path42.relative(absAppDir, abs));
15776
+ const rel = path44.normalize(path44.relative(absAppDir, abs));
15048
15777
  if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
15049
15778
  return true;
15050
15779
  }
@@ -15072,7 +15801,7 @@ class IncrementalBuilder {
15072
15801
  ${cssText}`).toString(36);
15073
15802
  const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
15074
15803
  const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
15075
- await Bun.write(path42.join(artifactDir, cssRelPath), cssText);
15804
+ await Bun.write(path44.join(artifactDir, cssRelPath), cssText);
15076
15805
  cssAssetEntries.push([basePath2, { cssUrl, cssRelPath }]);
15077
15806
  cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
15078
15807
  })()
@@ -15140,10 +15869,10 @@ ${cssText}`).toString(36);
15140
15869
  if (changedFiles.length === 0)
15141
15870
  return false;
15142
15871
  return changedFiles.some((file) => {
15143
- const normalized = path42.resolve(file);
15872
+ const normalized = path44.resolve(file);
15144
15873
  if (/\.(woff2?|ttf|otf)$/i.test(normalized))
15145
15874
  return true;
15146
- return this.#optimizedFonts.files.some((fontFile) => path42.resolve(fontFile) === normalized);
15875
+ return this.#optimizedFonts.files.some((fontFile) => path44.resolve(fontFile) === normalized);
15147
15876
  });
15148
15877
  }
15149
15878
  async installWatcher() {