@akanjs/cli 2.3.11-rc.3 → 2.3.11-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.
@@ -1710,7 +1710,8 @@ var appRootAllowedFiles = new Set([
1710
1710
  "main.ts",
1711
1711
  "package.json",
1712
1712
  "server.ts",
1713
- "tsconfig.json"
1713
+ "tsconfig.json",
1714
+ "tsconfig.tsbuildinfo"
1714
1715
  ]);
1715
1716
  var appRootAllowedDirs = new Set([
1716
1717
  ".akan",
@@ -4170,6 +4171,7 @@ var BACKEND_RESTART_DEBOUNCE_MS = 120;
4170
4171
  var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
4171
4172
  var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
4172
4173
  var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
4174
+ var BACKEND_STDERR_TAIL_LIMIT = 40;
4173
4175
  var BUILDER_READY_TIMEOUT_MS = 150000;
4174
4176
  var BUILDER_START_MAX_ATTEMPTS = 3;
4175
4177
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
@@ -4372,6 +4374,7 @@ class AkanAppHost {
4372
4374
  #pendingRestartReason = null;
4373
4375
  #backendStartStatus = null;
4374
4376
  #backendBuildStatusGeneration = 0;
4377
+ #backendStderrTail = [];
4375
4378
  #lastGoodFrontend = {};
4376
4379
  #buildStatusByPhase = new Map;
4377
4380
  #pendingBuildStatusReplay = [];
@@ -4423,6 +4426,7 @@ class AkanAppHost {
4423
4426
  this.#backendStartStatus = startStatus;
4424
4427
  this.#setBackendLifecycleState("starting");
4425
4428
  this.#backendReady = false;
4429
+ this.#backendStderrTail = [];
4426
4430
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
4427
4431
  cwd: this.app.workspace.workspaceRoot,
4428
4432
  stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
@@ -4456,6 +4460,37 @@ class AkanAppHost {
4456
4460
  });
4457
4461
  this.#backend = backend;
4458
4462
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
4463
+ if (this.withInk) {
4464
+ this.#forwardBackendStream(backend.stderr, "stderr");
4465
+ this.#forwardBackendStream(backend.stdout, "stdout");
4466
+ }
4467
+ }
4468
+ #recordBackendStderr(chunk) {
4469
+ const lines = chunk.split(/\r?\n/).filter((line) => line.length > 0);
4470
+ if (lines.length === 0)
4471
+ return;
4472
+ this.#backendStderrTail.push(...lines);
4473
+ if (this.#backendStderrTail.length > BACKEND_STDERR_TAIL_LIMIT) {
4474
+ this.#backendStderrTail.splice(0, this.#backendStderrTail.length - BACKEND_STDERR_TAIL_LIMIT);
4475
+ }
4476
+ }
4477
+ async#forwardBackendStream(stream, kind) {
4478
+ if (!stream)
4479
+ return;
4480
+ const decoder = new TextDecoder;
4481
+ try {
4482
+ for await (const chunk of stream) {
4483
+ const text = decoder.decode(chunk, { stream: true });
4484
+ if (!text.trim())
4485
+ continue;
4486
+ if (kind === "stderr") {
4487
+ this.#recordBackendStderr(text);
4488
+ this.logger.warn(`[backend] ${text.trimEnd()}`);
4489
+ } else {
4490
+ this.logger.verbose(`[backend] ${text.trimEnd()}`);
4491
+ }
4492
+ }
4493
+ } catch {}
4459
4494
  }
4460
4495
  #nextBackendBuildStatusGeneration(generation) {
4461
4496
  if (typeof generation === "number") {
@@ -4580,6 +4615,11 @@ class AkanAppHost {
4580
4615
  });
4581
4616
  this.#sendOrQueueBuildStatus(failureStatus);
4582
4617
  this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
4618
+ if (this.#backendStderrTail.length > 0) {
4619
+ this.logger.warn(`[backend-recovery] recent backend stderr:
4620
+ ${this.#backendStderrTail.join(`
4621
+ `)}`);
4622
+ }
4583
4623
  this.#backendRecoveryTimer = setTimeout(() => {
4584
4624
  this.#backendRecoveryTimer = null;
4585
4625
  if (this.#backend)
@@ -5945,6 +5985,117 @@ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${
5945
5985
  return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
5946
5986
  };
5947
5987
  var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
5988
+ var hasSourceParseErrors = (content, fileName = "source.ts") => hasParseDiagnostics(sourceFileFor(fileName, content));
5989
+ var findClassDeclaration = (source, className) => {
5990
+ let found = null;
5991
+ const visit = (node) => {
5992
+ if (found)
5993
+ return;
5994
+ if (ts4.isClassDeclaration(node) && node.name?.text === className) {
5995
+ found = node;
5996
+ return;
5997
+ }
5998
+ ts4.forEachChild(node, visit);
5999
+ };
6000
+ ts4.forEachChild(source, visit);
6001
+ return found;
6002
+ };
6003
+ var firstHeritageCall = (node) => {
6004
+ const expression = (node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [])[0]?.expression;
6005
+ return expression && ts4.isCallExpression(expression) ? expression : null;
6006
+ };
6007
+ var factoryArrowOf = (call) => {
6008
+ const arg = call.arguments.find((argument) => ts4.isArrowFunction(argument) || ts4.isFunctionExpression(argument));
6009
+ return arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg)) ? arg : null;
6010
+ };
6011
+ var hasClassMethod = (content, className, methodName) => {
6012
+ const node = findClassDeclaration(sourceFileFor("service.ts", content), className);
6013
+ return Boolean(node?.members.some((member) => ts4.isMethodDeclaration(member) && nodeName(member.name) === methodName));
6014
+ };
6015
+ var insertClassMethod = (content, className, methodBlock) => {
6016
+ const source = sourceFileFor("service.ts", content);
6017
+ const node = findClassDeclaration(source, className);
6018
+ if (!node)
6019
+ return null;
6020
+ const closeBrace = node.getEnd() - 1;
6021
+ if (content[closeBrace] !== "}")
6022
+ return null;
6023
+ const lead = content.slice(0, closeBrace).endsWith(`
6024
+ `) ? "" : `
6025
+ `;
6026
+ return spliceText(content, closeBrace, closeBrace, `${lead}${methodBlock}
6027
+ `);
6028
+ };
6029
+ var arrowParamInnerRegion = (content, source, arrow) => {
6030
+ if (arrow.parameters.length > 0) {
6031
+ return {
6032
+ start: arrow.parameters[0].getStart(source),
6033
+ end: arrow.parameters[arrow.parameters.length - 1].getEnd()
6034
+ };
6035
+ }
6036
+ const open = content.indexOf("(", arrow.getStart(source));
6037
+ if (open < 0)
6038
+ return null;
6039
+ const close = content.indexOf(")", open);
6040
+ if (close < 0)
6041
+ return null;
6042
+ return { start: open + 1, end: close };
6043
+ };
6044
+ var factoryParamEdit = (content, source, arrow, plan) => {
6045
+ if (arrow.parameters.length > 1)
6046
+ return null;
6047
+ const region = arrowParamInnerRegion(content, source, arrow);
6048
+ if (!region)
6049
+ return null;
6050
+ if (arrow.parameters.length === 0) {
6051
+ return { ...region, text: plan.mode === "destructure" ? `{ ${plan.name} }` : plan.name };
6052
+ }
6053
+ const param = arrow.parameters[0];
6054
+ if (plan.mode === "positional")
6055
+ return nodeName(param.name) === plan.name ? "unchanged" : null;
6056
+ if (!ts4.isObjectBindingPattern(param.name))
6057
+ return null;
6058
+ const names = param.name.elements.map((element) => ts4.isIdentifier(element.name) ? element.name.text : null);
6059
+ if (names.some((name) => name === null))
6060
+ return null;
6061
+ if (names.includes(plan.name))
6062
+ return "unchanged";
6063
+ return {
6064
+ start: param.getStart(source),
6065
+ end: param.getEnd(),
6066
+ text: `{ ${[...names, plan.name].join(", ")} }`
6067
+ };
6068
+ };
6069
+ var factoryObjectOf = (source, className) => {
6070
+ const node = findClassDeclaration(source, className);
6071
+ const call = node ? firstHeritageCall(node) : null;
6072
+ const arrow = call ? factoryArrowOf(call) : null;
6073
+ const object = arrow ? firstObjectReturnedByArrow(arrow) : null;
6074
+ return arrow && object ? { arrow, object } : null;
6075
+ };
6076
+ var hasSignalFactoryEntry = (content, className, entryName) => {
6077
+ const located = factoryObjectOf(sourceFileFor("signal.ts", content), className);
6078
+ return Boolean(located?.object.properties.some((property) => propertyName(property) === entryName));
6079
+ };
6080
+ var insertSignalFactoryEntry = (content, className, entryName, entryLine, param) => {
6081
+ const source = sourceFileFor("signal.ts", content);
6082
+ const located = factoryObjectOf(source, className);
6083
+ if (!located)
6084
+ return null;
6085
+ const locator = locatedObject(source, located.object);
6086
+ if (locator.fields.some((field) => field.name === entryName))
6087
+ return content;
6088
+ const paramEdit = factoryParamEdit(content, source, located.arrow, param);
6089
+ if (paramEdit === null)
6090
+ return null;
6091
+ const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
6092
+ fieldIndent: " ",
6093
+ closingIndent: ""
6094
+ });
6095
+ if (withEntry === content)
6096
+ return null;
6097
+ return paramEdit === "unchanged" ? withEntry : spliceText(withEntry, paramEdit.start, paramEdit.end, paramEdit.text);
6098
+ };
5948
6099
 
5949
6100
  // pkgs/@akanjs/devkit/workflow/moduleIndex.ts
5950
6101
  var indexFileKinds = [
@@ -6662,7 +6813,9 @@ var createWorkflowStepRegistry = ({
6662
6813
  createScalar,
6663
6814
  createUi,
6664
6815
  addField,
6665
- addEnumField
6816
+ addEnumField,
6817
+ addMutation,
6818
+ addSlice
6666
6819
  }) => {
6667
6820
  const inspect = async () => {
6668
6821
  return;
@@ -6738,7 +6891,19 @@ var createWorkflowStepRegistry = ({
6738
6891
  defaultValue: workflowStringInput(plan.inputs.default)
6739
6892
  })),
6740
6893
  [workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
6741
- [workflowStepKey("add-enum-field", "update-option")]: inspect
6894
+ [workflowStepKey("add-enum-field", "update-option")]: inspect,
6895
+ [workflowStepKey("add-mutation", "update-service")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addMutation({
6896
+ app: workflowStringInput(plan.inputs.app),
6897
+ module: workflowStringInput(plan.inputs.module),
6898
+ mutation: workflowStringInput(plan.inputs.mutation)
6899
+ })),
6900
+ [workflowStepKey("add-mutation", "update-signal")]: inspect,
6901
+ [workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addSlice({
6902
+ app: workflowStringInput(plan.inputs.app),
6903
+ module: workflowStringInput(plan.inputs.module),
6904
+ slice: workflowStringInput(plan.inputs.slice)
6905
+ })),
6906
+ [workflowStepKey("add-slice", "update-signal-slice")]: inspect
6742
6907
  };
6743
6908
  };
6744
6909
  class WorkflowExecutor {
@@ -12807,11 +12972,15 @@ async function resolveSignalTestPreloadPath(target) {
12807
12972
  addResolvedPackageCandidate(path39.dirname(Bun.main));
12808
12973
  addResolvedPackageCandidate(import.meta.dir);
12809
12974
  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));
12810
- for (const candidate of [...new Set(candidates)]) {
12975
+ const uniqueCandidates = [...new Set(candidates)];
12976
+ for (const candidate of uniqueCandidates) {
12811
12977
  if (await Bun.file(candidate).exists())
12812
12978
  return candidate;
12813
12979
  }
12814
- throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}`);
12980
+ throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
12981
+ Probed paths:
12982
+ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
12983
+ `)}`);
12815
12984
  }
12816
12985
  // pkgs/@akanjs/devkit/builder.ts
12817
12986
  import { existsSync as existsSync2 } from "fs";
package/index.js CHANGED
@@ -1708,7 +1708,8 @@ var appRootAllowedFiles = new Set([
1708
1708
  "main.ts",
1709
1709
  "package.json",
1710
1710
  "server.ts",
1711
- "tsconfig.json"
1711
+ "tsconfig.json",
1712
+ "tsconfig.tsbuildinfo"
1712
1713
  ]);
1713
1714
  var appRootAllowedDirs = new Set([
1714
1715
  ".akan",
@@ -4168,6 +4169,7 @@ var BACKEND_RESTART_DEBOUNCE_MS = 120;
4168
4169
  var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
4169
4170
  var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
4170
4171
  var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
4172
+ var BACKEND_STDERR_TAIL_LIMIT = 40;
4171
4173
  var BUILDER_READY_TIMEOUT_MS = 150000;
4172
4174
  var BUILDER_START_MAX_ATTEMPTS = 3;
4173
4175
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
@@ -4370,6 +4372,7 @@ class AkanAppHost {
4370
4372
  #pendingRestartReason = null;
4371
4373
  #backendStartStatus = null;
4372
4374
  #backendBuildStatusGeneration = 0;
4375
+ #backendStderrTail = [];
4373
4376
  #lastGoodFrontend = {};
4374
4377
  #buildStatusByPhase = new Map;
4375
4378
  #pendingBuildStatusReplay = [];
@@ -4421,6 +4424,7 @@ class AkanAppHost {
4421
4424
  this.#backendStartStatus = startStatus;
4422
4425
  this.#setBackendLifecycleState("starting");
4423
4426
  this.#backendReady = false;
4427
+ this.#backendStderrTail = [];
4424
4428
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
4425
4429
  cwd: this.app.workspace.workspaceRoot,
4426
4430
  stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
@@ -4454,6 +4458,37 @@ class AkanAppHost {
4454
4458
  });
4455
4459
  this.#backend = backend;
4456
4460
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
4461
+ if (this.withInk) {
4462
+ this.#forwardBackendStream(backend.stderr, "stderr");
4463
+ this.#forwardBackendStream(backend.stdout, "stdout");
4464
+ }
4465
+ }
4466
+ #recordBackendStderr(chunk) {
4467
+ const lines = chunk.split(/\r?\n/).filter((line) => line.length > 0);
4468
+ if (lines.length === 0)
4469
+ return;
4470
+ this.#backendStderrTail.push(...lines);
4471
+ if (this.#backendStderrTail.length > BACKEND_STDERR_TAIL_LIMIT) {
4472
+ this.#backendStderrTail.splice(0, this.#backendStderrTail.length - BACKEND_STDERR_TAIL_LIMIT);
4473
+ }
4474
+ }
4475
+ async#forwardBackendStream(stream, kind) {
4476
+ if (!stream)
4477
+ return;
4478
+ const decoder = new TextDecoder;
4479
+ try {
4480
+ for await (const chunk of stream) {
4481
+ const text = decoder.decode(chunk, { stream: true });
4482
+ if (!text.trim())
4483
+ continue;
4484
+ if (kind === "stderr") {
4485
+ this.#recordBackendStderr(text);
4486
+ this.logger.warn(`[backend] ${text.trimEnd()}`);
4487
+ } else {
4488
+ this.logger.verbose(`[backend] ${text.trimEnd()}`);
4489
+ }
4490
+ }
4491
+ } catch {}
4457
4492
  }
4458
4493
  #nextBackendBuildStatusGeneration(generation) {
4459
4494
  if (typeof generation === "number") {
@@ -4578,6 +4613,11 @@ class AkanAppHost {
4578
4613
  });
4579
4614
  this.#sendOrQueueBuildStatus(failureStatus);
4580
4615
  this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
4616
+ if (this.#backendStderrTail.length > 0) {
4617
+ this.logger.warn(`[backend-recovery] recent backend stderr:
4618
+ ${this.#backendStderrTail.join(`
4619
+ `)}`);
4620
+ }
4581
4621
  this.#backendRecoveryTimer = setTimeout(() => {
4582
4622
  this.#backendRecoveryTimer = null;
4583
4623
  if (this.#backend)
@@ -5943,6 +5983,117 @@ ${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${
5943
5983
  return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
5944
5984
  };
5945
5985
  var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
5986
+ var hasSourceParseErrors = (content, fileName = "source.ts") => hasParseDiagnostics(sourceFileFor(fileName, content));
5987
+ var findClassDeclaration = (source, className) => {
5988
+ let found = null;
5989
+ const visit = (node) => {
5990
+ if (found)
5991
+ return;
5992
+ if (ts4.isClassDeclaration(node) && node.name?.text === className) {
5993
+ found = node;
5994
+ return;
5995
+ }
5996
+ ts4.forEachChild(node, visit);
5997
+ };
5998
+ ts4.forEachChild(source, visit);
5999
+ return found;
6000
+ };
6001
+ var firstHeritageCall = (node) => {
6002
+ const expression = (node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [])[0]?.expression;
6003
+ return expression && ts4.isCallExpression(expression) ? expression : null;
6004
+ };
6005
+ var factoryArrowOf = (call) => {
6006
+ const arg = call.arguments.find((argument) => ts4.isArrowFunction(argument) || ts4.isFunctionExpression(argument));
6007
+ return arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg)) ? arg : null;
6008
+ };
6009
+ var hasClassMethod = (content, className, methodName) => {
6010
+ const node = findClassDeclaration(sourceFileFor("service.ts", content), className);
6011
+ return Boolean(node?.members.some((member) => ts4.isMethodDeclaration(member) && nodeName(member.name) === methodName));
6012
+ };
6013
+ var insertClassMethod = (content, className, methodBlock) => {
6014
+ const source = sourceFileFor("service.ts", content);
6015
+ const node = findClassDeclaration(source, className);
6016
+ if (!node)
6017
+ return null;
6018
+ const closeBrace = node.getEnd() - 1;
6019
+ if (content[closeBrace] !== "}")
6020
+ return null;
6021
+ const lead = content.slice(0, closeBrace).endsWith(`
6022
+ `) ? "" : `
6023
+ `;
6024
+ return spliceText(content, closeBrace, closeBrace, `${lead}${methodBlock}
6025
+ `);
6026
+ };
6027
+ var arrowParamInnerRegion = (content, source, arrow) => {
6028
+ if (arrow.parameters.length > 0) {
6029
+ return {
6030
+ start: arrow.parameters[0].getStart(source),
6031
+ end: arrow.parameters[arrow.parameters.length - 1].getEnd()
6032
+ };
6033
+ }
6034
+ const open = content.indexOf("(", arrow.getStart(source));
6035
+ if (open < 0)
6036
+ return null;
6037
+ const close = content.indexOf(")", open);
6038
+ if (close < 0)
6039
+ return null;
6040
+ return { start: open + 1, end: close };
6041
+ };
6042
+ var factoryParamEdit = (content, source, arrow, plan) => {
6043
+ if (arrow.parameters.length > 1)
6044
+ return null;
6045
+ const region = arrowParamInnerRegion(content, source, arrow);
6046
+ if (!region)
6047
+ return null;
6048
+ if (arrow.parameters.length === 0) {
6049
+ return { ...region, text: plan.mode === "destructure" ? `{ ${plan.name} }` : plan.name };
6050
+ }
6051
+ const param = arrow.parameters[0];
6052
+ if (plan.mode === "positional")
6053
+ return nodeName(param.name) === plan.name ? "unchanged" : null;
6054
+ if (!ts4.isObjectBindingPattern(param.name))
6055
+ return null;
6056
+ const names = param.name.elements.map((element) => ts4.isIdentifier(element.name) ? element.name.text : null);
6057
+ if (names.some((name) => name === null))
6058
+ return null;
6059
+ if (names.includes(plan.name))
6060
+ return "unchanged";
6061
+ return {
6062
+ start: param.getStart(source),
6063
+ end: param.getEnd(),
6064
+ text: `{ ${[...names, plan.name].join(", ")} }`
6065
+ };
6066
+ };
6067
+ var factoryObjectOf = (source, className) => {
6068
+ const node = findClassDeclaration(source, className);
6069
+ const call = node ? firstHeritageCall(node) : null;
6070
+ const arrow = call ? factoryArrowOf(call) : null;
6071
+ const object = arrow ? firstObjectReturnedByArrow(arrow) : null;
6072
+ return arrow && object ? { arrow, object } : null;
6073
+ };
6074
+ var hasSignalFactoryEntry = (content, className, entryName) => {
6075
+ const located = factoryObjectOf(sourceFileFor("signal.ts", content), className);
6076
+ return Boolean(located?.object.properties.some((property) => propertyName(property) === entryName));
6077
+ };
6078
+ var insertSignalFactoryEntry = (content, className, entryName, entryLine, param) => {
6079
+ const source = sourceFileFor("signal.ts", content);
6080
+ const located = factoryObjectOf(source, className);
6081
+ if (!located)
6082
+ return null;
6083
+ const locator = locatedObject(source, located.object);
6084
+ if (locator.fields.some((field) => field.name === entryName))
6085
+ return content;
6086
+ const paramEdit = factoryParamEdit(content, source, located.arrow, param);
6087
+ if (paramEdit === null)
6088
+ return null;
6089
+ const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
6090
+ fieldIndent: " ",
6091
+ closingIndent: ""
6092
+ });
6093
+ if (withEntry === content)
6094
+ return null;
6095
+ return paramEdit === "unchanged" ? withEntry : spliceText(withEntry, paramEdit.start, paramEdit.end, paramEdit.text);
6096
+ };
5946
6097
 
5947
6098
  // pkgs/@akanjs/devkit/workflow/moduleIndex.ts
5948
6099
  var indexFileKinds = [
@@ -6660,7 +6811,9 @@ var createWorkflowStepRegistry = ({
6660
6811
  createScalar,
6661
6812
  createUi,
6662
6813
  addField,
6663
- addEnumField
6814
+ addEnumField,
6815
+ addMutation,
6816
+ addSlice
6664
6817
  }) => {
6665
6818
  const inspect = async () => {
6666
6819
  return;
@@ -6736,7 +6889,19 @@ var createWorkflowStepRegistry = ({
6736
6889
  defaultValue: workflowStringInput(plan.inputs.default)
6737
6890
  })),
6738
6891
  [workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
6739
- [workflowStepKey("add-enum-field", "update-option")]: inspect
6892
+ [workflowStepKey("add-enum-field", "update-option")]: inspect,
6893
+ [workflowStepKey("add-mutation", "update-service")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addMutation({
6894
+ app: workflowStringInput(plan.inputs.app),
6895
+ module: workflowStringInput(plan.inputs.module),
6896
+ mutation: workflowStringInput(plan.inputs.mutation)
6897
+ })),
6898
+ [workflowStepKey("add-mutation", "update-signal")]: inspect,
6899
+ [workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addSlice({
6900
+ app: workflowStringInput(plan.inputs.app),
6901
+ module: workflowStringInput(plan.inputs.module),
6902
+ slice: workflowStringInput(plan.inputs.slice)
6903
+ })),
6904
+ [workflowStepKey("add-slice", "update-signal-slice")]: inspect
6740
6905
  };
6741
6906
  };
6742
6907
  class WorkflowExecutor {
@@ -12805,11 +12970,15 @@ async function resolveSignalTestPreloadPath(target) {
12805
12970
  addResolvedPackageCandidate(path39.dirname(Bun.main));
12806
12971
  addResolvedPackageCandidate(import.meta.dir);
12807
12972
  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));
12808
- for (const candidate of [...new Set(candidates)]) {
12973
+ const uniqueCandidates = [...new Set(candidates)];
12974
+ for (const candidate of uniqueCandidates) {
12809
12975
  if (await Bun.file(candidate).exists())
12810
12976
  return candidate;
12811
12977
  }
12812
- throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}`);
12978
+ throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
12979
+ Probed paths:
12980
+ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
12981
+ `)}`);
12813
12982
  }
12814
12983
  // pkgs/@akanjs/devkit/builder.ts
12815
12984
  import { existsSync as existsSync2 } from "fs";
@@ -15860,19 +16029,12 @@ var targetPaths = {
15860
16029
  "agents-md": "AGENTS.md",
15861
16030
  claude: "CLAUDE.md"
15862
16031
  };
15863
- var targetTitle = {
15864
- cursor: "Akan Cursor Rules",
15865
- "agents-md": "Akan Agent Rules",
15866
- claude: "Akan Claude Rules"
15867
- };
15868
- var renderAgentRules = async (workspace, target) => {
16032
+ var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
16033
+ var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
16034
+ var renderManagedBlock = async (workspace) => {
15869
16035
  const context = await AkanContextAnalyzer.analyze(workspace);
15870
16036
  const frameworkGuide = await Prompter.getInstruction("framework");
15871
- return `# ${targetTitle[target]}
15872
-
15873
- This file is generated by \`akan agent install ${target}\`.
15874
-
15875
- ## Workspace
16037
+ return `## Workspace
15876
16038
 
15877
16039
  - Repo: ${context.repoName}
15878
16040
  - Apps: ${context.apps.map((app) => app.name).join(", ") || "none"}
@@ -15909,20 +16071,75 @@ ${context.validationCommands.map((command3) => `- \`${command3}\``).join(`
15909
16071
 
15910
16072
  ## Framework Guide
15911
16073
 
15912
- ${frameworkGuide.trim()}
16074
+ ${frameworkGuide.trim()}`;
16075
+ };
16076
+ var upsertManagedBlock = (existing, block) => {
16077
+ const managed = `${AGENT_BLOCK_START}
16078
+ ${block}
16079
+ ${AGENT_BLOCK_END}`;
16080
+ const startIndex = existing.indexOf(AGENT_BLOCK_START);
16081
+ const endIndex = existing.indexOf(AGENT_BLOCK_END);
16082
+ if (startIndex >= 0 && endIndex > startIndex) {
16083
+ return `${existing.slice(0, startIndex)}${managed}${existing.slice(endIndex + AGENT_BLOCK_END.length)}`;
16084
+ }
16085
+ return `${existing.replace(/\s*$/, "")}
16086
+
16087
+ ${managed}
16088
+ `;
16089
+ };
16090
+ var renderAgentsMd = async (workspace, existing) => {
16091
+ const block = await renderManagedBlock(workspace);
16092
+ if (existing?.trim())
16093
+ return upsertManagedBlock(existing, block);
16094
+ const context = await AkanContextAnalyzer.analyze(workspace);
16095
+ return `# ${context.repoName} Agent Guide
16096
+
16097
+ This file is the single source of truth for coding agents in this workspace. Claude Code reads it through
16098
+ \`CLAUDE.md\` (\`@AGENTS.md\`) and Cursor through \`.cursor/rules/akan.mdc\`. The section between the
16099
+ \`akan:agent\` markers is regenerated by \`akan agent install\`; edit anything outside the markers freely.
16100
+
16101
+ ${AGENT_BLOCK_START}
16102
+ ${block}
16103
+ ${AGENT_BLOCK_END}
15913
16104
  `;
15914
16105
  };
16106
+ var renderClaudeMd = async (workspace) => {
16107
+ const context = await AkanContextAnalyzer.analyze(workspace);
16108
+ return `# ${context.repoName} \u2014 Claude Code Guide
16109
+
16110
+ @AGENTS.md
16111
+ `;
16112
+ };
16113
+ var renderCursorRule = () => `---
16114
+ description: Akan workspace agent guide
16115
+ alwaysApply: true
16116
+ ---
16117
+
16118
+ Follow the workspace agent guide, which is the single source of truth for Akan conventions,
16119
+ generated-file rules, and the MCP workflow policy.
16120
+
16121
+ @AGENTS.md
16122
+ `;
16123
+ var renderTarget = async (workspace, target, existing) => {
16124
+ if (target === "agents-md")
16125
+ return await renderAgentsMd(workspace, existing);
16126
+ if (target === "claude")
16127
+ return await renderClaudeMd(workspace);
16128
+ return renderCursorRule();
16129
+ };
15915
16130
 
15916
16131
  class AgentRunner extends runner("agent") {
15917
16132
  async install(workspace, targets, { force = false } = {}) {
15918
16133
  const written = [];
15919
16134
  for (const target of targets) {
15920
16135
  const filePath = targetPaths[target];
15921
- if (!force && await workspace.exists(filePath)) {
16136
+ const exists2 = await workspace.exists(filePath);
16137
+ if (exists2 && !force && target !== "agents-md") {
15922
16138
  throw new Error(`${filePath} already exists. Re-run with --force to overwrite it.`);
15923
16139
  }
15924
- const content = await renderAgentRules(workspace, target);
15925
- await workspace.writeFile(filePath, content, { overwrite: force });
16140
+ const existing = exists2 ? await workspace.readFile(filePath) : null;
16141
+ const content = await renderTarget(workspace, target, existing);
16142
+ await workspace.writeFile(filePath, content, { overwrite: true });
15926
16143
  written.push(filePath);
15927
16144
  }
15928
16145
  return written;
@@ -17997,8 +18214,18 @@ var addMutationWorkflowSpec = {
17997
18214
  }
17998
18215
  ],
17999
18216
  predictedChanges: [
18000
- { target: "*/lib/<module>/<module>.service.ts", action: "modify", reason: "Service mutation may be added." },
18001
- { target: "*/lib/<module>/<module>.signal.ts", action: "modify", reason: "Signal mutation may be added." },
18217
+ {
18218
+ target: "*/lib/<module>/<module>.service.ts",
18219
+ action: "modify",
18220
+ applyScope: "auto",
18221
+ reason: "Service mutation method stub is added."
18222
+ },
18223
+ {
18224
+ target: "*/lib/<module>/<module>.signal.ts",
18225
+ action: "modify",
18226
+ applyScope: "auto",
18227
+ reason: "Signal endpoint mutation is added."
18228
+ },
18002
18229
  { target: "*/lib/srv.ts", action: "sync", reason: "Generated service barrel may change after sync." },
18003
18230
  { target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
18004
18231
  ],
@@ -18070,9 +18297,24 @@ var addSliceWorkflowSpec = {
18070
18297
  }
18071
18298
  ],
18072
18299
  predictedChanges: [
18073
- { target: "*/lib/<module>/<module>.service.ts", action: "modify", reason: "Query helper may be added." },
18074
- { target: "*/lib/<module>/<module>.signal.ts", action: "modify", reason: "Signal slice may be added." },
18075
- { target: "*/lib/<module>/<Module>.Zone.tsx", action: "modify", reason: "Zone may connect slice state." },
18300
+ {
18301
+ target: "*/lib/<module>/<module>.service.ts",
18302
+ action: "modify",
18303
+ applyScope: "auto",
18304
+ reason: "Query helper method stub is added."
18305
+ },
18306
+ {
18307
+ target: "*/lib/<module>/<module>.signal.ts",
18308
+ action: "modify",
18309
+ applyScope: "auto",
18310
+ reason: "Signal slice entry is added."
18311
+ },
18312
+ {
18313
+ target: "*/lib/<module>/<Module>.Zone.tsx",
18314
+ action: "modify",
18315
+ applyScope: "manual-review",
18316
+ reason: "Zone may connect slice state."
18317
+ },
18076
18318
  { target: "*/lib/sig.ts", action: "sync", reason: "Generated signal barrel may change after sync." }
18077
18319
  ],
18078
18320
  validation: [
@@ -19384,6 +19626,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
19384
19626
  code: "primitive-field-type-unsupported",
19385
19627
  message: `Field type "${input7.type}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
19386
19628
  input: "type"
19629
+ } : null,
19630
+ input7.type && input7.type.toLowerCase() === "upload" ? {
19631
+ severity: "error",
19632
+ code: "primitive-field-type-upload-misuse",
19633
+ message: "Upload is not a model field type. Declare an image/file field as a relation to the File model (e.g. field(File)); Upload is only valid in a { fileUpload: true } signal body. Note the File model is provided by the shared file library.",
19634
+ input: "type"
19387
19635
  } : null
19388
19636
  ]);
19389
19637
  if (!sys3 || !input7.module || !input7.field || !input7.type || diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
@@ -19582,6 +19830,206 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
19582
19830
  nextActions: nextActionsForTarget(sys3.name)
19583
19831
  });
19584
19832
  }
19833
+ async addMutation(workspace, input7) {
19834
+ const moduleClassName = input7.module ? moduleComponentName(input7.module) : "";
19835
+ const serviceRef = lowerlize(moduleClassName);
19836
+ const name = input7.mutation;
19837
+ return await this.#writeServiceSignalEntry(workspace, {
19838
+ command: "add-mutation",
19839
+ app: input7.app,
19840
+ module: input7.module,
19841
+ entryKind: "mutation",
19842
+ entryName: name,
19843
+ requiredInput: "mutation",
19844
+ serviceMethod: name ? {
19845
+ name,
19846
+ block: [
19847
+ ` async ${name}() {`,
19848
+ ` // TODO(service): implement ${moduleClassName}Service.${name}`,
19849
+ ` return true;`,
19850
+ ` }`
19851
+ ].join(`
19852
+ `)
19853
+ } : null,
19854
+ signal: name ? {
19855
+ className: `${moduleClassName}Endpoint`,
19856
+ entryLine: [
19857
+ `${name}: mutation(Boolean)`,
19858
+ ` .exec(async function () {`,
19859
+ ` return await this.${serviceRef}Service.${name}();`,
19860
+ ` }),`
19861
+ ].join(`
19862
+ `),
19863
+ param: { mode: "destructure", name: "mutation" }
19864
+ } : null
19865
+ });
19866
+ }
19867
+ async addSlice(workspace, input7) {
19868
+ const moduleClassName = input7.module ? moduleComponentName(input7.module) : "";
19869
+ const serviceRef = lowerlize(moduleClassName);
19870
+ const name = input7.slice;
19871
+ const queryName = name ? `query${capitalize12(name)}` : null;
19872
+ return await this.#writeServiceSignalEntry(workspace, {
19873
+ command: "add-slice",
19874
+ app: input7.app,
19875
+ module: input7.module,
19876
+ entryKind: "slice",
19877
+ entryName: name,
19878
+ requiredInput: "slice",
19879
+ serviceMethod: name && queryName ? {
19880
+ name: queryName,
19881
+ block: [
19882
+ ` ${queryName}() {`,
19883
+ ` // TODO(service): return a QueryOf for the ${name} slice`,
19884
+ ` return {};`,
19885
+ ` }`
19886
+ ].join(`
19887
+ `)
19888
+ } : null,
19889
+ signal: name && queryName ? {
19890
+ className: `${moduleClassName}Slice`,
19891
+ entryLine: [
19892
+ `${name}: init()`,
19893
+ ` .exec(function () {`,
19894
+ ` return this.${serviceRef}Service.${queryName}();`,
19895
+ ` }),`
19896
+ ].join(`
19897
+ `),
19898
+ param: { mode: "positional", name: "init" }
19899
+ } : null
19900
+ });
19901
+ }
19902
+ async#writeServiceSignalEntry(workspace, spec) {
19903
+ const sys3 = await this.resolveSys(workspace, spec.app);
19904
+ const diagnostics = compactDiagnostics([
19905
+ !sys3 && { severity: "error", code: "primitive-target-missing", message: "Target app or library was not found." },
19906
+ !spec.module && {
19907
+ severity: "error",
19908
+ code: "primitive-input-missing",
19909
+ message: "Module is required.",
19910
+ input: "module"
19911
+ },
19912
+ !spec.entryName && {
19913
+ severity: "error",
19914
+ code: "primitive-input-missing",
19915
+ message: `${capitalize12(spec.requiredInput)} name is required.`,
19916
+ input: spec.requiredInput
19917
+ }
19918
+ ]);
19919
+ if (!sys3 || !spec.module || !spec.entryName || !spec.serviceMethod || !spec.signal) {
19920
+ return createPrimitiveWriteReport({
19921
+ command: spec.command,
19922
+ changedFiles: [],
19923
+ generatedFiles: [],
19924
+ validationCommands: [],
19925
+ diagnostics,
19926
+ nextActions: []
19927
+ });
19928
+ }
19929
+ const paths = moduleSourcePaths(spec.module);
19930
+ const servicePath = paths.service;
19931
+ const signalPath = paths.signal;
19932
+ const changedFiles = [];
19933
+ const generatedFiles2 = generatedFilesForSync(sys3);
19934
+ const [hasServiceFile, hasSignalFile] = await Promise.all([sys3.exists(servicePath), sys3.exists(signalPath)]);
19935
+ if (!hasServiceFile) {
19936
+ diagnostics.push({
19937
+ severity: "error",
19938
+ code: "primitive-source-missing",
19939
+ message: `Service source file was not found: ${servicePath}.`
19940
+ });
19941
+ }
19942
+ if (!hasSignalFile) {
19943
+ diagnostics.push({
19944
+ severity: "error",
19945
+ code: "primitive-source-missing",
19946
+ message: `Signal source file was not found: ${signalPath}.`
19947
+ });
19948
+ }
19949
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
19950
+ return createPrimitiveWriteReport({
19951
+ command: spec.command,
19952
+ changedFiles,
19953
+ generatedFiles: generatedFiles2,
19954
+ validationCommands: validationCommandsForTarget(sys3.name),
19955
+ diagnostics,
19956
+ nextActions: nextActionsForTarget(sys3.name)
19957
+ });
19958
+ }
19959
+ const serviceClassName = `${moduleComponentName(spec.module)}Service`;
19960
+ const serviceContent = await sys3.readFile(servicePath);
19961
+ const signalContent = await sys3.readFile(signalPath);
19962
+ const serviceMethodExists = hasClassMethod(serviceContent, serviceClassName, spec.serviceMethod.name);
19963
+ if (serviceMethodExists) {
19964
+ diagnostics.push({
19965
+ severity: "error",
19966
+ code: "primitive-service-method-exists",
19967
+ input: spec.requiredInput,
19968
+ message: `Method "${spec.serviceMethod.name}" already exists in ${serviceClassName}.`
19969
+ });
19970
+ }
19971
+ if (hasSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName)) {
19972
+ diagnostics.push({
19973
+ severity: "error",
19974
+ code: "primitive-signal-entry-exists",
19975
+ input: spec.requiredInput,
19976
+ message: `Entry "${spec.entryName}" already exists in ${spec.signal.className}.`
19977
+ });
19978
+ }
19979
+ const nextServiceContent = serviceMethodExists ? serviceContent : insertClassMethod(serviceContent, serviceClassName, spec.serviceMethod.block);
19980
+ const nextSignalContent = insertSignalFactoryEntry(signalContent, spec.signal.className, spec.entryName, spec.signal.entryLine, spec.signal.param);
19981
+ if (!nextServiceContent) {
19982
+ diagnostics.push({
19983
+ severity: "error",
19984
+ code: "primitive-service-shape-unsupported",
19985
+ message: `Could not find ${serviceClassName} class body in ${servicePath}.`
19986
+ });
19987
+ }
19988
+ if (!nextSignalContent) {
19989
+ diagnostics.push({
19990
+ severity: "error",
19991
+ code: "primitive-signal-shape-unsupported",
19992
+ message: `Could not find a safe insertion point in ${spec.signal.className} within ${signalPath}. Ensure the class exists and its factory returns an object literal.`
19993
+ });
19994
+ }
19995
+ if (nextServiceContent && hasSourceParseErrors(nextServiceContent, "service.ts")) {
19996
+ diagnostics.push({
19997
+ severity: "error",
19998
+ code: "primitive-post-edit-service-parse-failed",
19999
+ failureScope: "source-change",
20000
+ message: `Edited ${servicePath} did not parse cleanly; refusing to write source.`
20001
+ });
20002
+ }
20003
+ if (nextSignalContent && hasSourceParseErrors(nextSignalContent, "signal.ts")) {
20004
+ diagnostics.push({
20005
+ severity: "error",
20006
+ code: "primitive-post-edit-signal-parse-failed",
20007
+ failureScope: "source-change",
20008
+ message: `Edited ${signalPath} did not parse cleanly; refusing to write source.`
20009
+ });
20010
+ }
20011
+ if (nextSignalContent && !hasSignalFactoryEntry(nextSignalContent, spec.signal.className, spec.entryName)) {
20012
+ diagnostics.push({
20013
+ severity: "error",
20014
+ code: "primitive-post-edit-signal-verify-failed",
20015
+ failureScope: "source-change",
20016
+ message: `Edited ${signalPath} did not contain entry "${spec.entryName}" in ${spec.signal.className}.`
20017
+ });
20018
+ }
20019
+ if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextServiceContent && nextSignalContent) {
20020
+ await sys3.writeFile(servicePath, nextServiceContent);
20021
+ await sys3.writeFile(signalPath, nextSignalContent);
20022
+ changedFiles.push(sourceFile(sys3, servicePath, "modify", `Service ${spec.entryKind} was added.`), sourceFile(sys3, signalPath, "modify", `Signal ${spec.entryKind} was added.`));
20023
+ }
20024
+ return createPrimitiveWriteReport({
20025
+ command: spec.command,
20026
+ changedFiles,
20027
+ generatedFiles: generatedFiles2,
20028
+ validationCommands: validationCommandsForTarget(sys3.name),
20029
+ diagnostics,
20030
+ nextActions: nextActionsForTarget(sys3.name)
20031
+ });
20032
+ }
19585
20033
  }
19586
20034
 
19587
20035
  // pkgs/@akanjs/cli/scalar/scalar.prompt.ts
@@ -19799,7 +20247,9 @@ var createCliWorkflowStepRegistry = (workspace) => createWorkflowStepRegistry({
19799
20247
  createScalar: (sys3, scalar) => CommandContainer.get(ScalarScript).createScalar(sys3, scalar),
19800
20248
  createUi: (input8) => CommandContainer.get(PrimitiveScript).createUi(workspace, input8),
19801
20249
  addField: (input8) => CommandContainer.get(PrimitiveScript).addField(workspace, input8),
19802
- addEnumField: (input8) => CommandContainer.get(PrimitiveScript).addEnumField(workspace, input8)
20250
+ addEnumField: (input8) => CommandContainer.get(PrimitiveScript).addEnumField(workspace, input8),
20251
+ addMutation: (input8) => CommandContainer.get(PrimitiveScript).addMutation(workspace, input8),
20252
+ addSlice: (input8) => CommandContainer.get(PrimitiveScript).addSlice(workspace, input8)
19803
20253
  });
19804
20254
 
19805
20255
  // pkgs/@akanjs/cli/context/context.runner.ts
@@ -20990,7 +21440,9 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
20990
21440
  createScalar: (sys3, scalar) => this.scalarScript.createScalar(sys3, scalar),
20991
21441
  createUi: (input8) => this.primitiveScript.createUi(workspace, input8),
20992
21442
  addField: (input8) => this.primitiveScript.addField(workspace, input8),
20993
- addEnumField: (input8) => this.primitiveScript.addEnumField(workspace, input8)
21443
+ addEnumField: (input8) => this.primitiveScript.addEnumField(workspace, input8),
21444
+ addMutation: (input8) => this.primitiveScript.addMutation(workspace, input8),
21445
+ addSlice: (input8) => this.primitiveScript.addSlice(workspace, input8)
20994
21446
  })
20995
21447
  }));
20996
21448
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.3.11-rc.3",
3
+ "version": "2.3.11-rc.5",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -34,7 +34,7 @@
34
34
  "@langchain/openai": "^1.4.6",
35
35
  "@tailwindcss/node": "^4.3.0",
36
36
  "@trapezedev/project": "^7.1.4",
37
- "akanjs": "2.3.11-rc.3",
37
+ "akanjs": "2.3.11-rc.5",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "daisyui": "5.5.23",
@@ -1,38 +1,9 @@
1
1
  ---
2
- description: Akan.js workspace conventions for coding agents
2
+ description: Akan workspace agent guide
3
3
  alwaysApply: true
4
4
  ---
5
5
 
6
- # Akan.js Workspace Rules
6
+ Follow the workspace agent guide, which is the single source of truth for Akan conventions,
7
+ generated-file rules, and the MCP workflow policy.
7
8
 
8
- - Keep edits inside the established Akan structure: `apps/<app>`, `libs/<lib>`, `pkgs/*`, and `infra`.
9
- - Pages live under `apps/<app>/page`; index routes use `_index.tsx`, and nested layouts use `_layout.tsx`.
10
- - Database domain modules live under `lib/<model>`.
11
- - Service modules live under `lib/_<service>`.
12
- - Scalar modules live under `lib/__scalar/<scalar>`.
13
- - Module abstracts live beside module code as `<model>.abstract.md`, `<service>.abstract.md`, or
14
- `<scalar>.abstract.md`. Read them before changing module behavior.
15
- - Do not hand-edit generated Akan files such as `akan.app.json`, `client.ts`, `server.ts`, generated facet indexes,
16
- `lib/cnst.ts`, `lib/dict.ts`, `lib/db.ts`, `lib/srv.ts`, `lib/st.ts`, `lib/sig.ts`, `lib/useClient.ts`, or
17
- `lib/useServer.ts`.
18
- - Prefer Akan MCP workflows before direct source edits: use `akan mcp --mode plan` for workflow discovery and
19
- planning, then `akan mcp --mode apply` only for allowlisted apply, validation, and repair tools.
20
- - If `plan_workflow` returns `planPath` or `next.tool=apply_workflow`, call `apply_workflow({ planPath })` before
21
- editing source files directly.
22
- - After `apply_workflow`, run `run_validation` with `validationTarget` when present; otherwise use `applyReportPath`.
23
- - Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
24
- - Direct edits are fallback only after `list_workflows`/`explain_workflow` show no matching workflow, or after
25
- `apply_workflow` reports unsupported/no-op/failed diagnostics that require manual action.
26
- - For compound requests, split the request into workflows and apply each `planPath` in order, such as `create-module`
27
- followed by `add-field`.
28
- - If generated output is stale or broken, update the owning source file and run `akan repair generated` or
29
- `akan sync <app-or-lib>` instead of patching generated files.
30
- - For new domain behavior, inspect sibling `constant`, `dictionary`, `signal`, `document`, `service`, `store`, and UI
31
- module files before changing shape.
32
- - Update `*.abstract.md` when business invariants, workflows, or public behavior change. Do not update it for
33
- formatting-only, import-only, or style-only changes.
34
- - Respect server/client import boundaries. Use `akanjs/server` only in server-side code and `akanjs/client` only in
35
- client/page runtime code.
36
- - Treat `AKAN_PUBLIC_*` env vars as public values. Do not store secrets in them.
37
- - Verify changes with the smallest relevant command: `akan lint <target>`, `akan test <target>`, or
38
- `akan build <app-name>`.
9
+ @AGENTS.md
@@ -118,4 +118,5 @@ libs/*/client.ts
118
118
  libs/*/server.ts
119
119
  libs/*/index.ts
120
120
  **/.akan
121
- **/bun.lock
121
+ **/bun.lock
122
+ **/tsconfig.tsbuildinfo
@@ -576,3 +576,13 @@ akan sync automatically generates APIs across all layers. Only write custom logi
576
576
  | `insight[Query](args)`, `query[Query](args)` | Insight and raw query |
577
577
 
578
578
  **Rule**: Define `Filter` with `.query()` conditions in `document.ts`. akan sync auto-generates all 10 query helper methods per filter. Write `Document` chain methods only for state transitions with validation.
579
+
580
+ ## Generated Context
581
+
582
+ The section below is regenerated by `akan agent install` (run `bun run setup:agent`). It lists this
583
+ workspace's apps, generated files, validation commands, and framework guide. Edit anything outside the
584
+ markers freely; content between them is overwritten on the next install.
585
+
586
+ <!-- akan:agent:start -->
587
+ <!-- Populated by `akan agent install`. Run `bun run setup:agent` to refresh. -->
588
+ <!-- akan:agent:end -->