@akanjs/cli 3.0.0-beta.17 → 3.0.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build-stamp +1 -1
- package/{CodeAgent-gjqnc2mg.js → CodeAgent-6cm97kay.js} +2 -2
- package/{CodeAgentRpcHost-qvwfv0n9.js → CodeAgentRpcHost-p7h6n2hp.js} +1 -1
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/{CodeTui-nd9hqvrw.js → CodeTui-za4jsm41.js} +205 -34
- package/{TunnelShare-r5tc2qk5.js → TunnelShare-2j6s511d.js} +2 -2
- package/{agent.command-n664dtcv.js → agent.command-e0wpbbyg.js} +6 -6
- package/akanCode.js +7 -5
- package/{application.command-t5542jte.js → application.command-8wyxqc8q.js} +28 -15
- package/{applicationBuildRunner-x4gbfn7y.js → applicationBuildRunner-n9gzaxd0.js} +35 -10
- package/{applicationReleasePackager-z61ep017.js → applicationReleasePackager-vpggfqgq.js} +1 -1
- package/buildBatch.proc.js +3 -3
- package/{capacitorApp-shv3ajh5.js → capacitorApp-q3z0qht0.js} +2 -2
- package/{cloud.command-dk5tj39k.js → cloud.command-4zwa5dsx.js} +34 -12
- package/cloud.script-fwx3vb34.js +20 -0
- package/{code.command-88sebgnn.js → code.command-468c9d7s.js} +41 -19
- package/commandManifest.json +1 -1
- package/{context.command-pqsm5jrk.js → context.command-dsnw0jby.js} +14 -14
- package/context.runner-rs4rj15d.js +22 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-0z2h2tks.js} +1 -1
- package/{devTui-gy6mendg.js → devTui-ffjsarqa.js} +3 -3
- package/{devTuiView-y1548wy6.js → devTuiView-1q1mdgrv.js} +1 -1
- package/{guideline.command-rpc2emw8.js → guideline.command-vhw13pqr.js} +8 -13
- package/guidelines/agentRule/agentRule.instruction.md +27 -10
- package/guidelines/conventions/conventions.instruction.md +53 -26
- package/guidelines/queryRule/queryRule.instruction.md +17 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +4 -3
- package/incrementalBuilder.proc.js +3 -2
- package/{index-4n4821gp.js → index-10dkrrww.js} +4 -4
- package/{index-bzakk0c2.js → index-14zrtgfa.js} +5 -3
- package/{index-v17gj504.js → index-18zybgkc.js} +2 -2
- package/{index-qh7brn20.js → index-22xtgmhn.js} +6 -2
- package/{index-qhtj9y6j.js → index-2446k7b4.js} +74 -21
- package/{index-g9zghs4h.js → index-3jrp517k.js} +12 -12
- package/{index-vpx68e4x.js → index-4m929vvn.js} +2 -2
- package/{index-4cvpzkq6.js → index-53176s7j.js} +3 -3
- package/{index-jwrpsrg3.js → index-667w0yah.js} +1 -1
- package/{index-3mg4h788.js → index-7ep8qtvp.js} +2 -2
- package/{index-w8kmx1b6.js → index-89hy31kf.js} +30 -7
- package/index-9d6nvkz2.js +656 -0
- package/index-9dp031by.js +12 -0
- package/{index-zn8qgeqx.js → index-a1w6934z.js} +3 -3
- package/{index-p0tew3fq.js → index-ab0c38ea.js} +10 -5
- package/index-bc88j1ry.js +1166 -0
- package/{index-ns128c4q.js → index-bftkz6cp.js} +280 -73
- package/{index-kcxmv48c.js → index-bhtmj233.js} +4 -1
- package/{index-kvzjda3v.js → index-bwf965h6.js} +5 -5
- package/{index-p4p9t45x.js → index-crmeqnmf.js} +56 -44
- package/{index-8k16atqy.js → index-d2242ksy.js} +23 -6
- package/{index-2sq06vv0.js → index-e4v6vhxq.js} +417 -105
- package/{index-xjc7gg65.js → index-esk82rvk.js} +23 -33
- package/{index-sn7dnzq4.js → index-kwaq3n9n.js} +1 -1
- package/{index-gm76f0qb.js → index-n3cv0tm4.js} +1 -1
- package/{index-6na0sd2b.js → index-qdgzsnhg.js} +5 -2
- package/{index-twq9p2sp.js → index-s5rhm04y.js} +0 -6
- package/{index-n8kt2wh6.js → index-sq2dzkzm.js} +35 -10
- package/{index-myj01w9r.js → index-srmay5jw.js} +15 -2
- package/{index-2pm1j01n.js → index-txr7dj2g.js} +4 -4
- package/{index-3cymrzsb.js → index-vqx8bdxz.js} +17 -3
- package/{index-te0y1c0m.js → index-zqgj2rt2.js} +12 -7
- package/index.js +21 -21
- package/{library.command-m8p2r1dh.js → library.command-cqr175dy.js} +3 -3
- package/{localRegistry.command-yppdqrxv.js → localRegistry.command-49z55qqz.js} +9 -9
- package/{module.command-mfzvqt0n.js → module.command-9p8mm3b0.js} +5 -5
- package/{package.command-cgp2p2v5.js → package.command-wxyz909t.js} +5 -5
- package/package.json +2 -2
- package/{page.command-taenb02b.js → page.command-gsrsfqqs.js} +3 -3
- package/{primitive.command-7kpncmkk.js → primitive.command-ns1mptje.js} +6 -6
- package/{prompter-9mn3kp36.js → prompter-1f8d9pcc.js} +1 -1
- package/{quality.command-1ar94hrw.js → quality.command-e76k2q9k.js} +18 -17
- package/{repair.command-n08t6j8p.js → repair.command-pkrsm3cw.js} +5 -5
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-mq8qfp4t.js → scalar.command-0cpt3fc8.js} +4 -4
- package/skills/akan-code/SKILL.md +122 -0
- package/skills/akan-module/SKILL.md +2 -2
- package/skills/akan-store/SKILL.md +6 -2
- package/{subspace.command-jqv5n79w.js → subspace.command-fktpwp0g.js} +20 -10
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +1 -1
- package/templates/appSample/srvkit/AuthGuard.ts +4 -1
- package/templates/appSample/srvkit/SessionInternalArg.ts +3 -1
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/service/__model__.abstract.md.template +8 -0
- package/{tunnel.command-ht2es78c.js → tunnel.command-d5jy9gk8.js} +4 -4
- package/{workflow.command-tvz4nm9n.js → workflow.command-6tkcysmx.js} +9 -9
- package/{workspace.command-txv1yv5e.js → workspace.command-gv84eh1p.js} +25 -24
- package/cloud.script-ch3xrw3s.js +0 -20
- package/context.runner-bys6ykcp.js +0 -22
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -34
- package/index-57p9p179.js +0 -461
- package/index-qt8dqgnf.js +0 -250
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
import {
|
|
3
3
|
Workspace,
|
|
4
4
|
command
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-qdgzsnhg.js";
|
|
6
6
|
import"./index-q0y2jq9j.js";
|
|
7
|
-
import"./index-
|
|
7
|
+
import"./index-crmeqnmf.js";
|
|
8
8
|
import"./index-6wwzs1bc.js";
|
|
9
9
|
import {
|
|
10
10
|
renderPrimitiveReport
|
|
11
|
-
} from "./index-
|
|
12
|
-
import"./index-
|
|
13
|
-
import"./index-
|
|
11
|
+
} from "./index-2446k7b4.js";
|
|
12
|
+
import"./index-22xtgmhn.js";
|
|
13
|
+
import"./index-esk82rvk.js";
|
|
14
14
|
import {
|
|
15
15
|
PrimitiveScript
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-zqgj2rt2.js";
|
|
17
17
|
|
|
18
18
|
// pkgs/@akanjs/cli/primitive/primitive.command.ts
|
|
19
19
|
import { Logger } from "akanjs/common";
|
|
@@ -4,31 +4,32 @@ import {
|
|
|
4
4
|
runner,
|
|
5
5
|
script,
|
|
6
6
|
command
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-qdgzsnhg.js";
|
|
8
8
|
import"./index-q0y2jq9j.js";
|
|
9
9
|
import {
|
|
10
10
|
rootAllowedFiles,
|
|
11
11
|
rootAllowedDirs,
|
|
12
12
|
isAllowedLibFacetRootFile
|
|
13
|
-
} from "./index-
|
|
13
|
+
} from "./index-crmeqnmf.js";
|
|
14
14
|
import"./index-6wwzs1bc.js";
|
|
15
15
|
|
|
16
16
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
17
17
|
import { createHash } from "crypto";
|
|
18
18
|
import { readdir, readFile, stat } from "fs/promises";
|
|
19
|
-
import
|
|
19
|
+
import path2 from "path";
|
|
20
20
|
import { RESERVED_ROUTE_CONFIG_EXPORTS } from "akanjs/common";
|
|
21
21
|
import ignore from "ignore";
|
|
22
22
|
import ts3 from "typescript";
|
|
23
23
|
|
|
24
24
|
// pkgs/@akanjs/devkit/abstractDoc.ts
|
|
25
|
+
import path from "path";
|
|
26
|
+
|
|
25
27
|
class AbstractDoc {
|
|
26
28
|
path;
|
|
27
29
|
content;
|
|
28
30
|
siblingFiles;
|
|
29
31
|
static suffix = ".abstract.md";
|
|
30
32
|
static maxLines = 300;
|
|
31
|
-
static compactMinLines = 40;
|
|
32
33
|
static #facetRoots = "{lib,ui,webkit,srvkit,common,plugin}";
|
|
33
34
|
static isAbstractPath(filePath) {
|
|
34
35
|
return filePath.endsWith(AbstractDoc.suffix);
|
|
@@ -47,7 +48,7 @@ class AbstractDoc {
|
|
|
47
48
|
return folder.startsWith("_") ? "service" : "domain";
|
|
48
49
|
}
|
|
49
50
|
static async findAll(sys, { module } = {}) {
|
|
50
|
-
const filePaths = (await sys.getAllFiles(`${AbstractDoc.#facetRoots}/**/*${AbstractDoc.suffix}`)).sort();
|
|
51
|
+
const filePaths = (await sys.getAllFiles(`${AbstractDoc.#facetRoots}/**/*${AbstractDoc.suffix}`)).map((filePath) => filePath.split(path.sep).join("/")).sort();
|
|
51
52
|
const docs = await Promise.all(filePaths.map((filePath) => AbstractDoc.read(sys, filePath)));
|
|
52
53
|
if (!module)
|
|
53
54
|
return docs;
|
|
@@ -58,8 +59,8 @@ class AbstractDoc {
|
|
|
58
59
|
const [content, folderFiles] = await Promise.all([sys.readFile(filePath), sys.readdir(dirPath)]);
|
|
59
60
|
return new AbstractDoc(filePath, content, folderFiles.filter((fileName) => !AbstractDoc.isAbstractPath(fileName)));
|
|
60
61
|
}
|
|
61
|
-
constructor(
|
|
62
|
-
this.path =
|
|
62
|
+
constructor(path2, content, siblingFiles) {
|
|
63
|
+
this.path = path2;
|
|
63
64
|
this.content = content;
|
|
64
65
|
this.siblingFiles = siblingFiles;
|
|
65
66
|
}
|
|
@@ -551,7 +552,7 @@ var RULE_FIXES = {
|
|
|
551
552
|
"akan.global.duplicate-exported-function-body": "Extract the shared implementation into a single exported helper and import it, instead of copying the body.",
|
|
552
553
|
"akan.file.recommended-max-lines": "Split the file by responsibility \u2014 move Zones, Utils, or subcomponents into sibling files.",
|
|
553
554
|
"akan.file.max-lines": "Break the file into smaller focused modules; keep one primary responsibility per file.",
|
|
554
|
-
"akan.file.abstract-max-lines": "
|
|
555
|
+
"akan.file.abstract-max-lines": "Trim the abstract by hand to a title, one sentence naming what the module owns, a `## Rules` list of the invariants the source cannot show, and an optional workflow chain.",
|
|
555
556
|
"akan.file.placeholder-export": "Remove the placeholder export; generated indexes should only re-export real modules.",
|
|
556
557
|
"akan.file.bang-comment-in-client": "Rewrite the marker as `// FIXME:` or `// TODO:`. Keep the bang form for server, srvkit, and CLI files.",
|
|
557
558
|
"akan.file.dictionary-stale-text": "Replace the scaffold text with real localized copy for this dictionary entry.",
|
|
@@ -609,7 +610,7 @@ class AkanQualityScanner {
|
|
|
609
610
|
const ignoreFilter = ignore().add(await this.#readGitIgnore(workspaceRoot));
|
|
610
611
|
const files = [];
|
|
611
612
|
for (const targetRoot of ["apps", "libs"]) {
|
|
612
|
-
const absoluteTargetRoot =
|
|
613
|
+
const absoluteTargetRoot = path2.join(workspaceRoot, targetRoot);
|
|
613
614
|
if (!await isDirectory(absoluteTargetRoot))
|
|
614
615
|
continue;
|
|
615
616
|
await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
|
|
@@ -617,7 +618,7 @@ class AkanQualityScanner {
|
|
|
617
618
|
return files.sort();
|
|
618
619
|
}
|
|
619
620
|
async#readGitIgnore(workspaceRoot) {
|
|
620
|
-
const gitIgnorePath =
|
|
621
|
+
const gitIgnorePath = path2.join(workspaceRoot, ".gitignore");
|
|
621
622
|
if (!await Bun.file(gitIgnorePath).exists())
|
|
622
623
|
return [];
|
|
623
624
|
return (await readFile(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
@@ -625,8 +626,8 @@ class AkanQualityScanner {
|
|
|
625
626
|
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
626
627
|
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
627
628
|
for (const entry of entries) {
|
|
628
|
-
const absolutePath =
|
|
629
|
-
const relativePath = toPosix(
|
|
629
|
+
const absolutePath = path2.join(currentPath, entry.name);
|
|
630
|
+
const relativePath = toPosix(path2.relative(workspaceRoot, absolutePath));
|
|
630
631
|
if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
|
|
631
632
|
continue;
|
|
632
633
|
if (entry.isDirectory()) {
|
|
@@ -641,7 +642,7 @@ class AkanQualityScanner {
|
|
|
641
642
|
}
|
|
642
643
|
}
|
|
643
644
|
async#readSourceFile(workspaceRoot, file) {
|
|
644
|
-
const absolutePath =
|
|
645
|
+
const absolutePath = path2.join(workspaceRoot, file);
|
|
645
646
|
const content = await readFile(absolutePath, "utf8");
|
|
646
647
|
return {
|
|
647
648
|
file,
|
|
@@ -651,7 +652,7 @@ class AkanQualityScanner {
|
|
|
651
652
|
};
|
|
652
653
|
}
|
|
653
654
|
async#readTextFile(workspaceRoot, file) {
|
|
654
|
-
return { file, content: await readFile(
|
|
655
|
+
return { file, content: await readFile(path2.join(workspaceRoot, file), "utf8") };
|
|
655
656
|
}
|
|
656
657
|
#scanGlobalQuality(sourceFiles) {
|
|
657
658
|
const exportedFunctionLikes = sourceFiles.flatMap((sourceFile) => getExportedFunctionLikes(sourceFile));
|
|
@@ -776,7 +777,7 @@ class AkanQualityScanner {
|
|
|
776
777
|
const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile.file.endsWith(candidate));
|
|
777
778
|
if (!suffix)
|
|
778
779
|
return [];
|
|
779
|
-
const modelName = toPascalCase(
|
|
780
|
+
const modelName = toPascalCase(path2.basename(sourceFile.file, suffix));
|
|
780
781
|
const warnings = [];
|
|
781
782
|
for (const declaration of getTopLevelDeclarations(sourceFile)) {
|
|
782
783
|
if (isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
@@ -787,7 +788,7 @@ class AkanQualityScanner {
|
|
|
787
788
|
severity: "warning",
|
|
788
789
|
file: sourceFile.file,
|
|
789
790
|
line: declaration.line,
|
|
790
|
-
message: `${
|
|
791
|
+
message: `${path2.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
|
|
791
792
|
});
|
|
792
793
|
}
|
|
793
794
|
return warnings;
|
|
@@ -1322,7 +1323,7 @@ function toPascalCase(value) {
|
|
|
1322
1323
|
return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
|
|
1323
1324
|
}
|
|
1324
1325
|
function toPosix(value) {
|
|
1325
|
-
return value.split(
|
|
1326
|
+
return value.split(path2.sep).join("/");
|
|
1326
1327
|
}
|
|
1327
1328
|
function groupBy(items, getKey) {
|
|
1328
1329
|
const grouped = new Map;
|
|
@@ -3,16 +3,16 @@ import {
|
|
|
3
3
|
Workspace,
|
|
4
4
|
script,
|
|
5
5
|
command
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-qdgzsnhg.js";
|
|
7
7
|
import"./index-q0y2jq9j.js";
|
|
8
|
-
import"./index-
|
|
8
|
+
import"./index-crmeqnmf.js";
|
|
9
9
|
import"./index-6wwzs1bc.js";
|
|
10
10
|
import"./index-by5gpp8x.js";
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
import"./index-2446k7b4.js";
|
|
12
|
+
import"./index-18zybgkc.js";
|
|
13
13
|
import {
|
|
14
14
|
RepairRunner
|
|
15
|
-
} from "./index-
|
|
15
|
+
} from "./index-53176s7j.js";
|
|
16
16
|
|
|
17
17
|
// pkgs/@akanjs/cli/repair/repair.script.ts
|
|
18
18
|
import { Logger } from "akanjs/common";
|
|
@@ -57,12 +57,6 @@ class RouteSourceValidator {
|
|
|
57
57
|
if (exported.has("head") && exported.has("generateHead")) {
|
|
58
58
|
throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
|
|
59
59
|
}
|
|
60
|
-
if (!options.rootLayout && (exported.has("head") || exported.has("generateHead")) && (exported.has("metadata") || exported.has("generateMetadata"))) {
|
|
61
|
-
throw new Error(`[route-convention] head/generateHead and metadata/generateMetadata cannot both be exported in ${filePath}`);
|
|
62
|
-
}
|
|
63
|
-
if (exported.has("metadata") && exported.has("generateMetadata")) {
|
|
64
|
-
throw new Error(`[route-convention] metadata and generateMetadata cannot both be exported in ${filePath}`);
|
|
65
|
-
}
|
|
66
60
|
const chain = RouteSourceValidator.#readChain(sourceFile, filePath);
|
|
67
61
|
if (!chain)
|
|
68
62
|
return { devOnly: RouteSourceValidator.#readDevOnly(sourceFile, filePath) };
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
Sys,
|
|
4
4
|
command
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-qdgzsnhg.js";
|
|
6
6
|
import"./index-q0y2jq9j.js";
|
|
7
|
-
import"./index-
|
|
7
|
+
import"./index-crmeqnmf.js";
|
|
8
8
|
import"./index-6wwzs1bc.js";
|
|
9
9
|
import {
|
|
10
10
|
renderPrimitiveReport
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-2446k7b4.js";
|
|
12
12
|
import {
|
|
13
13
|
ScalarScript
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-4m929vvn.js";
|
|
15
15
|
|
|
16
16
|
// pkgs/@akanjs/cli/scalar/scalar.command.ts
|
|
17
17
|
import { Logger, lowerlize } from "akanjs/common";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: akan-code
|
|
3
|
+
description: "Answer a question about `akan code` itself — how to add or remove an MCP server, sign one in, switch model, fork or resume a session, what the slash commands and keys are, and where its files live. Read this before telling anyone how to use this tool."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# akan code — the manual
|
|
7
|
+
|
|
8
|
+
This is the tool the user is talking to right now. When they ask how to do something *with it* — rather than
|
|
9
|
+
with their code — answer from here, and quote the exact gesture: the slash command to type, or the file to
|
|
10
|
+
edit. Do not guess a command; everything this tool offers is listed below.
|
|
11
|
+
|
|
12
|
+
You cannot type a slash command yourself. They are the host's, not yours — say which one to type. You *can*
|
|
13
|
+
edit the files named below, which is the useful half of most of these answers: write the change, then say
|
|
14
|
+
which command makes it take effect.
|
|
15
|
+
|
|
16
|
+
## MCP servers
|
|
17
|
+
|
|
18
|
+
Declared in **`.akan/code/mcp.json`**, in the same `mcpServers` shape Claude, Cursor and VS Code already
|
|
19
|
+
write, so a block can be pasted across rather than translated. VS Code's `servers` spelling is read too, and
|
|
20
|
+
whichever key the file already uses is the one a write goes back into.
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
|
|
26
|
+
"linear": { "url": "https://mcp.linear.app/sse" },
|
|
27
|
+
"internal": { "url": "https://…/mcp", "headers": { "x-api-key": "…" } },
|
|
28
|
+
"old": { "command": "x", "disabled": true }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A `url` is an http server; anything else is a command with its argv. `env` passes variables to a stdio
|
|
34
|
+
server, `headers` goes on every http request (a server behind a static token needs nothing else), and
|
|
35
|
+
`disabled: true` keeps an entry without connecting it.
|
|
36
|
+
|
|
37
|
+
From the prompt:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
/mcp what is declared, what connected, and how many tools each gave
|
|
41
|
+
/mcp <name> the tools that one server published
|
|
42
|
+
/mcp add <name> <command> [args…]
|
|
43
|
+
/mcp add <name> <https://…>
|
|
44
|
+
/mcp remove <name>
|
|
45
|
+
/mcp login <name> OAuth, in the browser
|
|
46
|
+
/mcp logout <name> forget the token, leaving the server declared
|
|
47
|
+
/mcp reload reopen this session so the file takes effect
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**A server added now is not reachable until the session reloads.** The tool allowlist is fixed when the
|
|
51
|
+
session is created, so `add`, `remove`, `login` and `logout` all reload by themselves — a session that has
|
|
52
|
+
said nothing yet has nothing on disk to reopen, and says so. If you edit `mcp.json` yourself, tell the user to
|
|
53
|
+
run `/mcp reload`.
|
|
54
|
+
|
|
55
|
+
Sign-in is discovered, not configured: the server's `401` names its resource metadata, that names the
|
|
56
|
+
authorization server, and most hosted servers register a client on demand — so a url is usually the whole
|
|
57
|
+
entry. For a provider that issues client ids by hand, add `"oauth": { "clientId": "…", "scope": "…" }`.
|
|
58
|
+
Tokens are stored per server in `~/.akan/code/mcpAuth.json` at mode 0600, never in the repo, and refreshed
|
|
59
|
+
silently; `/mcp` shows `sign-in needed` when a server wants one.
|
|
60
|
+
|
|
61
|
+
## Sessions
|
|
62
|
+
|
|
63
|
+
Stored under `.akan/code/sessions` — project history, so a second checkout of the same repo has its own.
|
|
64
|
+
|
|
65
|
+
- `←` at the start of the prompt lists past sessions; enter reopens the chosen one.
|
|
66
|
+
- `akan code --resume <id>` (`-R`) reopens one from the shell.
|
|
67
|
+
- `/fork [name]` continues the conversation twice: the copy takes over here, and the notice names the command
|
|
68
|
+
that reopens the original.
|
|
69
|
+
- `/name <text>` names this one, which is what the session list is read by.
|
|
70
|
+
- `/peers` lists other `akan code` sessions running in this workspace; `/msg <peer> <text>` hands one a
|
|
71
|
+
message.
|
|
72
|
+
- `/clear` wipes the screen and keeps the conversation; `/compact [notes]` shortens what the model carries.
|
|
73
|
+
|
|
74
|
+
## Model, effort, keys
|
|
75
|
+
|
|
76
|
+
- `/model <provider>/<id>`, e.g. `/model deepseek/deepseek-v4-pro`. `/effort <level>` for how hard it thinks.
|
|
77
|
+
- A provider is authorized by **`<PROVIDER>_API_KEY`** — `DEEPSEEK_API_KEY`, `ANTHROPIC_API_KEY`. The
|
|
78
|
+
workspace's own `.env` is read as well as the environment, and a key found there is used for the run and
|
|
79
|
+
never copied anywhere.
|
|
80
|
+
- Credentials and the model catalogue live in `~/.akan/code/` (`auth.json`, `models.json`). **`AKAN_CODE_HOME`
|
|
81
|
+
moves the whole directory** — for a container with no home, or a checkout that wants its own.
|
|
82
|
+
|
|
83
|
+
## What the agent reads, and how to change it
|
|
84
|
+
|
|
85
|
+
- Every `AGENTS.md` / `CLAUDE.md` from the workspace root down to the working directory, `.cursorrules`, and
|
|
86
|
+
any `.cursor/rules/*.mdc` that applies unconditionally (`alwaysApply: true`, or no frontmatter at all).
|
|
87
|
+
Edit those to change how this agent behaves in this repo — they are the files the editor beside it reads.
|
|
88
|
+
- Skills: drop a `<name>/SKILL.md` into `.akan/code/skills`. Extensions: `.akan/code/extensions`. Neither is
|
|
89
|
+
read from a home directory, so a session behaves the same in two checkouts.
|
|
90
|
+
|
|
91
|
+
## Keys
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
enter send · shift+enter newline · ↑↓ move (history at the edges) · ←→ move
|
|
95
|
+
esc interrupt · pgup/pgdn scroll · ^c quit · tab completes a slash command
|
|
96
|
+
@ completes a file in this repo · wheel scrolls the transcript (hold shift to select text instead)
|
|
97
|
+
cmd+v attaches an image from the clipboard · ^v does too, without the terminal in the way
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Most terminals send the same byte for enter and shift+enter, so the key only arrives where the terminal can
|
|
101
|
+
say which was pressed. In VS Code, bind it with `workbench.action.terminal.sendSequence` and a `text` of
|
|
102
|
+
`"\\\u001b\r"`; iTerm2 calls the same thing "Send Text". Either spelling of that sequence works here, and
|
|
103
|
+
the backslash a shell would read as a line continuation never reaches the prompt.
|
|
104
|
+
|
|
105
|
+
## Starting it
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
akan code open the session
|
|
109
|
+
akan code "<prompt>" run one turn and print the answer
|
|
110
|
+
akan code -i "<prompt>" open the session with the prompt already sent
|
|
111
|
+
--app <name> narrow the agent to one app
|
|
112
|
+
--profile local|pod|review|web what it may do (default local)
|
|
113
|
+
--model <provider>/<id> --thinking, --json, --rpc, --resume <id>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`review` reads and never writes, loads no project context files, and keeps no session on disk. `pod` has
|
|
117
|
+
nobody in front of it: it cannot ask a question. MCP is off in both.
|
|
118
|
+
|
|
119
|
+
## The rest of the commands
|
|
120
|
+
|
|
121
|
+
`/help` prints all of this list · `/tools` what this session can call · `/agents` what a sub-agent may be and
|
|
122
|
+
what one may spend · `/thinking` shows or folds the reasoning · `/abort` stops the running turn · `/quit`.
|
|
@@ -41,5 +41,5 @@ module. Collection-level helpers go `static` on the full model.
|
|
|
41
41
|
- Read the module's `<module>.abstract.md` before changing it, and update it when an invariant or workflow
|
|
42
42
|
changes — not for formatting.
|
|
43
43
|
|
|
44
|
-
Deeper detail per layer is a `get_guideline` call away: `moduleOverview`, `
|
|
45
|
-
`
|
|
44
|
+
Deeper detail per layer is a `get_guideline` call away: `moduleOverview`, `modelConstant`, `modelDocument`,
|
|
45
|
+
`modelService`, `modelSignal`, `modelStore`, `modelDictionary`.
|
|
@@ -37,14 +37,18 @@ Only what the generated set does not cover: extra client state, a multi-step or
|
|
|
37
37
|
calls to the non-CRUD endpoints you added in the signal. Each is an `async` method using `this.set(...)` /
|
|
38
38
|
`this.get()` / `this.pick(...)`.
|
|
39
39
|
|
|
40
|
+
To reach another store, cast `this`: `import type { RootStore } from "../st"`, then
|
|
41
|
+
`(this as unknown as RootStore).logout()` to call its action or `(this as unknown as RootStore).set({ … })` /
|
|
42
|
+
`.get()` to write or read its state. Every store is mixed into one root at runtime, so the cast only tells the
|
|
43
|
+
type what `this` already is. Keep it `import type` — `st.ts` imports every store, so a value import is a cycle.
|
|
44
|
+
|
|
40
45
|
A store that needs none of this stays empty. `store(sig.comment, () => ({}))` with an empty body is the
|
|
41
46
|
correct, complete result — an empty store is not an unfinished one.
|
|
42
47
|
|
|
43
|
-
##
|
|
48
|
+
## The rule the linter enforces
|
|
44
49
|
|
|
45
50
|
- **An action returns nothing.** Every method dispatches through `st.do.<action>()` and is typed `void`, so a
|
|
46
51
|
returned value is unreachable. Write it into state with `this.set({ … })`.
|
|
47
|
-
- **Never `import type { RootStore } from "../st"`** — it crashes `akan build` with a Bun SSR segfault.
|
|
48
52
|
|
|
49
53
|
## From UI
|
|
50
54
|
|
|
@@ -4,23 +4,23 @@ import {
|
|
|
4
4
|
} from "./index-q0y2jq9j.js";
|
|
5
5
|
import {
|
|
6
6
|
GlobalConfig
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-srmay5jw.js";
|
|
8
8
|
import {
|
|
9
9
|
Workspace,
|
|
10
10
|
CommandContainer,
|
|
11
11
|
runner,
|
|
12
12
|
script,
|
|
13
13
|
command
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-qdgzsnhg.js";
|
|
15
15
|
import {
|
|
16
16
|
Executor,
|
|
17
17
|
WorkspaceExecutor,
|
|
18
18
|
AppExecutor
|
|
19
|
-
} from "./index-
|
|
19
|
+
} from "./index-crmeqnmf.js";
|
|
20
20
|
import"./index-6wwzs1bc.js";
|
|
21
21
|
import {
|
|
22
22
|
SlicePlanner
|
|
23
|
-
} from "./index-
|
|
23
|
+
} from "./index-kwaq3n9n.js";
|
|
24
24
|
import {
|
|
25
25
|
LibSource
|
|
26
26
|
} from "./index-3d9y066c.js";
|
|
@@ -197,9 +197,20 @@ class Subspace {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
async#extract(paths, excludes = []) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
const archivePath = path.relative(this.#workspace.workspaceRoot, `${this.#clonePath}.tar`);
|
|
201
|
+
await this.#workspace.spawn("git", ["archive", "-o", archivePath, "HEAD", "--", ...paths]);
|
|
202
|
+
try {
|
|
203
|
+
await this.#workspace.spawn("tar", [
|
|
204
|
+
"-x",
|
|
205
|
+
"-f",
|
|
206
|
+
archivePath,
|
|
207
|
+
"-C",
|
|
208
|
+
this.#clonePath,
|
|
209
|
+
...excludes.map((entry) => `--exclude=${entry}`)
|
|
210
|
+
]);
|
|
211
|
+
} finally {
|
|
212
|
+
await rm(path.join(this.#workspace.workspaceRoot, archivePath), { force: true });
|
|
213
|
+
}
|
|
203
214
|
}
|
|
204
215
|
#localEnv() {
|
|
205
216
|
const { serveDomain } = WorkspaceExecutor.getBaseDevEnv();
|
|
@@ -269,8 +280,7 @@ class Subspace {
|
|
|
269
280
|
if (!await FileSys.fileExists(gitignorePath))
|
|
270
281
|
return;
|
|
271
282
|
const [begin = "", end = ""] = Subspace.secretsMarkers;
|
|
272
|
-
const lines = (await FileSys.readText(gitignorePath)).split(
|
|
273
|
-
`);
|
|
283
|
+
const lines = (await FileSys.readText(gitignorePath)).split(/\r?\n/);
|
|
274
284
|
const beginIdx = lines.indexOf(begin);
|
|
275
285
|
const endIdx = lines.indexOf(end);
|
|
276
286
|
const filtered = beginIdx >= 0 && endIdx > beginIdx ? [
|
|
@@ -725,7 +735,7 @@ class SubspaceRunner extends runner("subspace") {
|
|
|
725
735
|
if (!asked)
|
|
726
736
|
return { name: subspace.name, workspaceId, host, apps, libs: [], outcome: "cancelled", files: [] };
|
|
727
737
|
const { libs } = await subspace.slice();
|
|
728
|
-
const { CloudScript } = await import("./cloud.script-
|
|
738
|
+
const { CloudScript } = await import("./cloud.script-fwx3vb34.js");
|
|
729
739
|
const { files } = await CommandContainer.get(CloudScript).uploadEnv(workspace, {
|
|
730
740
|
host,
|
|
731
741
|
workspaceId,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# <%= model %> Abstract
|
|
2
|
+
|
|
3
|
+
<One sentence: the value this scalar holds, and what embeds it.>
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
- <What a caller may assume about the value that the constant file cannot say: an order, a unit, a fixed value.>
|
|
8
|
+
- <Two to five of them. A single field's meaning goes in a trailing comment beside it in the constant file.>
|
|
@@ -3,45 +3,14 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "workHistory.abstract.md",
|
|
6
|
-
content: `#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Standard scalar layers:
|
|
15
|
-
- workHistory.constant.ts — enum (WorkHistoryAction) + entry class (WorkHistory) defined via via() + enumOf()
|
|
16
|
-
- workHistory.dictionary.ts — i18n via scalarDictionary() (fewer layers than modelDictionary)
|
|
17
|
-
- workHistory.abstract.md — embedding rules and intent
|
|
18
|
-
|
|
19
|
-
## Convention: lib/__scalar/<type>/ directory naming
|
|
20
|
-
|
|
21
|
-
- Scalar modules use double-underscore prefix: lib/__scalar/workHistory/
|
|
22
|
-
- Files drop the underscores: workHistory.constant.ts, not __workHistory.constant.ts
|
|
23
|
-
- Models embed scalars via field([WorkHistory]) — list-of-scalar embedding
|
|
24
|
-
|
|
25
|
-
## Embedding Example (in task.constant.ts)
|
|
26
|
-
|
|
27
|
-
TaskObject embeds a list of WorkHistory:
|
|
28
|
-
import { WorkHistory } from "../../__scalar/workHistory/workHistory.constant";
|
|
29
|
-
export class TaskObject extends via(TaskInput, (field) => ({
|
|
30
|
-
workHistory: field([WorkHistory]),
|
|
31
|
-
}))
|
|
32
|
-
|
|
33
|
-
Each status change (create, start, complete) pushes a new entry.
|
|
34
|
-
|
|
35
|
-
## Agent Notes
|
|
36
|
-
|
|
37
|
-
- Read this abstract before modifying the WorkHistory scalar.
|
|
38
|
-
- Update when new action types or fields are added.
|
|
39
|
-
- Do not duplicate workHistory fields in individual models; always reuse this scalar.
|
|
40
|
-
|
|
41
|
-
## Related Modules
|
|
42
|
-
|
|
43
|
-
- lib/task/ — database module embedding workHistory as a list field
|
|
44
|
-
- lib/_noti/ — pure service module (different module type, no scalar usage)
|
|
6
|
+
content: `# workHistory Abstract
|
|
7
|
+
One step in a task's history: which move happened, when, and an optional note.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
- Entries are only appended — by \`Task\`'s chain methods and by the task service on create — and never edited or
|
|
11
|
+
removed, so the list reads as the task's moves in order.
|
|
12
|
+
- \`at\` is the moment the entry was built when the writer gives none.
|
|
13
|
+
- \`note\` is empty on every entry the app writes itself.
|
|
45
14
|
`,
|
|
46
15
|
};
|
|
47
16
|
}
|
|
@@ -3,36 +3,13 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "noti.abstract.md",
|
|
6
|
-
content: `#
|
|
6
|
+
content: `# noti Service Abstract
|
|
7
|
+
Noti holds the in-app notification list on the client: the messages the app shows, an unread count, and dismissal.
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Both module types follow the same layer pattern, but service modules have fewer layers:
|
|
14
|
-
no constant.ts (no data shape), no document.ts (no DB queries), no UI components.
|
|
15
|
-
|
|
16
|
-
- noti.service.ts — business logic via serve("noti" as const, ...) (named service, no DB binding)
|
|
17
|
-
- noti.signal.ts — real-time pubsub endpoint bound via endpoint(srv.noti, ...)
|
|
18
|
-
- noti.store.ts — client state via store("noti" as const, ...) (named store, no signal binding)
|
|
19
|
-
- noti.dictionary.ts — i18n via serviceDictionary() (no model field labels)
|
|
20
|
-
|
|
21
|
-
## Convention: lib/_<service>/ directory naming
|
|
22
|
-
|
|
23
|
-
- Service modules live under lib/_<service>/ — the underscore prefix signals "this is a service, not a database model."
|
|
24
|
-
- Files drop the underscore: lib/_noti/noti.service.ts, not lib/_noti/_noti.service.ts.
|
|
25
|
-
- Scan registers into the same barrel files: srv.ts, sig.ts, st.ts, dict.ts.
|
|
26
|
-
|
|
27
|
-
## Convention: named serve/store (no DB binding)
|
|
28
|
-
|
|
29
|
-
- serve("noti" as const, ...) — service name passed as string, no db.<module> binding.
|
|
30
|
-
- store("noti" as const, ...) — store name passed as string, no sig.<module> binding.
|
|
31
|
-
|
|
32
|
-
## Related Modules
|
|
33
|
-
|
|
34
|
-
- lib/task/ — database module reference (compare structure differences)
|
|
35
|
-
- lib/__scalar/workHistory/ — reusable scalar field modules (double-underscore prefix, embedded in models)
|
|
9
|
+
## Rules
|
|
10
|
+
- Notifications live only in the client store; the server side declares nothing yet, so a reload starts empty.
|
|
11
|
+
- \`unreadCount\` counts additions since the last \`markAllRead()\`, not the list's length — removing a notification
|
|
12
|
+
does not lower it.
|
|
36
13
|
`,
|
|
37
14
|
};
|
|
38
15
|
}
|
|
@@ -3,38 +3,18 @@ import type { AppInfo, LibInfo } from "akanjs";
|
|
|
3
3
|
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
4
|
return {
|
|
5
5
|
filename: "task.abstract.md",
|
|
6
|
-
content: `#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- task.service.ts — business logic orchestration bound to DB via serve(db.task, ...)
|
|
19
|
-
- task.signal.ts — API surface (query, mutation, pubsub endpoints bound via endpoint(srv.task, ...))
|
|
20
|
-
- task.store.ts — client state management bound to signal via store(sig.task, ...)
|
|
21
|
-
- Task.*.tsx — UI components (Zone -> Load -> Unit / View / Template / Util)
|
|
22
|
-
|
|
23
|
-
## Convention: lib/<module>/ directory naming
|
|
24
|
-
|
|
25
|
-
- Database modules live under lib/<module>/ (no underscore prefix)
|
|
26
|
-
- Files follow <module>.<layer>.ts naming — e.g., task.constant.ts, task.service.ts
|
|
27
|
-
- UI files use PascalCase with explicit suffix: Task.Zone.tsx, Task.Unit.tsx, Task.View.tsx
|
|
28
|
-
|
|
29
|
-
## Convention: scan-registered barrels
|
|
30
|
-
|
|
31
|
-
akan sync auto-discovers each file and registers it in the corresponding barrel:
|
|
32
|
-
cnst.ts, db.ts, dict.ts, srv.ts, sig.ts, st.ts
|
|
33
|
-
|
|
34
|
-
## Related Modules
|
|
35
|
-
|
|
36
|
-
- lib/__scalar/workHistory/ — standard scalar modules with double-underscore prefix (reusable field sets)
|
|
37
|
-
- lib/_noti/ — pure service module (compare structure differences: no constant/document, serve without DB binding)
|
|
6
|
+
content: `# task Abstract
|
|
7
|
+
A task is one unit of work that moves from todo through in progress to completed, and keeps a record of each move.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
- A task only moves forward: \`start()\` needs \`todo\` and \`complete()\` needs \`inProgress\`; any other state throws.
|
|
11
|
+
- Every move appends one \`workHistory\` entry, and creation writes the first (\`created\`), so the history is never
|
|
12
|
+
empty and its last entry always matches \`status\`.
|
|
13
|
+
- Anyone may read a task; creating, changing, starting and completing one needs a signed-in caller. There is no
|
|
14
|
+
generated admin API (\`root: None\`).
|
|
15
|
+
- Removal is soft: a removed task keeps its row with \`removedAt\` set.
|
|
16
|
+
|
|
17
|
+
todo -> inProgress -> completed
|
|
38
18
|
`,
|
|
39
19
|
};
|
|
40
20
|
}
|