@akanjs/devkit 3.0.0-beta.9 โ 3.0.0
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/DEV_RUNTIME_KNOBS.md +44 -26
- package/README.ko.md +21 -7
- package/README.md +17 -8
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +56 -3
- package/akanApp/akanApp.host.ts +218 -278
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +81 -35
- package/akanApp/devHostPolicy.ts +41 -102
- package/akanConfig/akanBin.test.ts +62 -0
- package/akanConfig/akanBin.ts +78 -0
- package/akanConfig/akanConfig.test.ts +439 -141
- package/akanConfig/akanConfig.ts +359 -275
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +22 -8
- package/akanContext.ts +110 -160
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +41 -5
- package/applicationBuildRunner.ts +71 -63
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +32 -30
- package/artifact/implicitRootLayout.ts +58 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +35 -2
- package/biomeBase.ts +2 -5
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +71 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/csrE2e/csrE2eHarness.fixture.ts +505 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +252 -0
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/registryVendor.e2e.test.ts +221 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +176 -106
- package/executors.ts +212 -498
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts โ fileSys.test.ts} +5 -3
- package/fileSys.ts +22 -9
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +123 -37
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +16 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +122 -110
- package/frontendBuild/csrArtifactBuilder.ts +9 -31
- package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
- package/frontendBuild/csrDevArtifactWriter.ts +307 -0
- package/frontendBuild/csrDevBundler.test.ts +66 -0
- package/frontendBuild/csrDevBundler.ts +95 -0
- package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
- package/frontendBuild/csrDevFactoryCheck.ts +39 -0
- package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
- package/frontendBuild/csrDevModuleCompiler.ts +422 -0
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +145 -0
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +363 -0
- package/frontendBuild/csrDevTypes.ts +61 -0
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +31 -82
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +57 -70
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +59 -32
- package/frontendBuild/hmrWatcher.ts +31 -74
- package/frontendBuild/index.ts +4 -0
- package/frontendBuild/pagesBundleBuilder.ts +28 -30
- package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +100 -62
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
- package/frontendBuild/ssrDevBundler.ts +85 -0
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +147 -63
- package/incrementalBuilder/buildBatchProtocol.ts +13 -38
- package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
- package/incrementalBuilder/buildBatchRunner.ts +26 -38
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
- package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
- package/index.ts +5 -19
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +105 -199
- package/integration/devStabilityHarness.test.ts +13 -28
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
- package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
- package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
- package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-document-cookie.grit +29 -0
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/lint/no-web-only-api-outside-webkit.grit +44 -0
- package/lint/no-web-storage.grit +30 -0
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/desktopBin.test.ts +182 -0
- package/mobile/desktopBin.ts +144 -0
- package/mobile/desktopServerStage.test.ts +252 -0
- package/mobile/desktopServerStage.ts +178 -0
- package/mobile/index.ts +9 -0
- package/mobile/mobileTarget.test.ts +57 -8
- package/mobile/mobileTarget.ts +45 -26
- package/mobile/nativeApi.test.ts +30 -0
- package/mobile/nativeApi.ts +40 -0
- package/mobile/nativeApp.test.ts +184 -0
- package/mobile/nativeApp.ts +454 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +438 -0
- package/mobile/nativeConfig.ts +294 -0
- package/mobile/nativePluginFolders.test.ts +81 -0
- package/mobile/nativePluginFolders.ts +60 -0
- package/mobile/nativeWebDir.test.ts +80 -0
- package/mobile/nativeWebDir.ts +66 -0
- package/mobile/placeholderAppId.test.ts +13 -0
- package/mobile/placeholderAppId.ts +16 -0
- package/mobile/usageDescriptions.test.ts +27 -0
- package/mobile/usageDescriptions.ts +24 -0
- package/package.json +11 -11
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +200 -286
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +91 -225
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/stripAnsi.ts +3 -0
- package/subspace.test.ts +9 -25
- package/subspace.ts +48 -115
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +194 -0
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +42 -20
- package/transforms/serverEnvPlugin.test.ts +103 -0
- package/transforms/serverEnvPlugin.ts +41 -0
- package/transforms/transforms.test.ts +166 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +6 -23
- package/typeChecker.ts +7 -63
- package/types.ts +2 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.test.ts +18 -3
- package/workspaceLayout.ts +22 -23
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/applicationReleasePackager.ts +0 -209
- package/capacitorApp.test.ts +0 -372
- package/capacitorApp.ts +0 -1648
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
package/workspaceLayout.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* ๊ฐ์ ๊ท์น์ scanInfo(`akan sync`, hard error) ยท akanContext(`akan doctor`, diagnostic) ยท
|
|
5
|
-
* qualityScanner(`akan quality scan`, warning) ์ธ ๊ณณ์ด ๊ฐ์ ๋ณต์ฌํด ๋๋ฉด์ ์ค์ ๋ก ์ด๊ธ๋ฌ๋ค
|
|
6
|
-
* (์ค์ฝํ AGENTS.md/CLAUDE.md ๋ sync ๋ง ํ์ฉ, `plugin` ์ ๋ฌธ์์๋ง, `secrets` ๋ doctor ๋ง ๊ฑฐ๋ถ).
|
|
7
|
-
* ๊ท์น์ ์ถ๊ฐํ ๋๋ ์ด ํ์ผ๋ง ๊ณ ์น๊ณ , ๋ฃจํธ AGENTS.md ์ ๋ชฉ๋ก๋ ๊ฐ์ด ๊ฐฑ์ ํ๋ค.
|
|
8
|
-
*/
|
|
1
|
+
// The one source of the app/lib root allowlists (sync, doctor and quality scan read it); mirror a change into the
|
|
2
|
+
// root AGENTS.md list.
|
|
9
3
|
|
|
10
4
|
export type SysType = "app" | "lib";
|
|
11
5
|
|
|
12
6
|
export const appRootAllowedFiles = new Set([
|
|
13
|
-
// ์ค์ฝํ ์์ด์ ํธ ๊ฐ์ด๋ โ scan(write) ์ด ์ ์งํ๋ ์์ธ + ๋ง์ปค ๋ฐ hand-written ๋ด์ฉ (agentsIndex.ts)
|
|
14
7
|
"AGENTS.md",
|
|
15
8
|
"CLAUDE.md",
|
|
16
9
|
"akan.app.json",
|
|
17
10
|
"akan.config.ts",
|
|
18
|
-
"capacitor.config.ts",
|
|
19
11
|
"client.ts",
|
|
20
12
|
"main.ts",
|
|
21
13
|
"package.json",
|
|
@@ -26,12 +18,10 @@ export const appRootAllowedFiles = new Set([
|
|
|
26
18
|
|
|
27
19
|
export const appRootAllowedDirs = new Set([
|
|
28
20
|
".akan",
|
|
29
|
-
"android",
|
|
30
21
|
"common",
|
|
31
22
|
"env",
|
|
32
|
-
"ios",
|
|
33
23
|
"lib",
|
|
34
|
-
"
|
|
24
|
+
"native",
|
|
35
25
|
"page",
|
|
36
26
|
"plugin",
|
|
37
27
|
"private",
|
|
@@ -43,12 +33,7 @@ export const appRootAllowedDirs = new Set([
|
|
|
43
33
|
"webkit",
|
|
44
34
|
]);
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
* ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฃจํธ๋ ์ฑ ๋ฃจํธ์์ ์คํ/๋ชจ๋ฐ์ผ ์ ์ฉ ํญ๋ชฉ(`main.ts`, `capacitor.config.ts`, `.akan`,
|
|
48
|
-
* `android`, `ios`, `mobile`, `script`, `secrets`)์ ๋บ ์งํฉ์ด๋ค โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ถํ
๋์ง๋, ํจํค์ง๋์ง๋
|
|
49
|
-
* ์๋๋ค. `index.ts` ๋ lib ์๋ง ์์ฑ๋๊ณ , `README.md` / `tsconfig.spec.json` ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํจํค์ง๋ก
|
|
50
|
-
* ๋ฐฐํฌ๋๊ธฐ ๋๋ฌธ์ ๋จ๋๋ค. `public` / `private` ์ syncAssets ๊ฐ ์ฑ์ผ๋ก ์ฌ๋งํฌํ๋ฏ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ฐ์ง๋ค.
|
|
51
|
-
*/
|
|
36
|
+
// A library is never booted or packaged as an app, so the run/mobile entries are out; it is published, so README stays.
|
|
52
37
|
export const libRootAllowedFiles = new Set([
|
|
53
38
|
"AGENTS.md",
|
|
54
39
|
"CLAUDE.md",
|
|
@@ -68,6 +53,7 @@ export const libRootAllowedDirs = new Set([
|
|
|
68
53
|
"common",
|
|
69
54
|
"env",
|
|
70
55
|
"lib",
|
|
56
|
+
"native",
|
|
71
57
|
"page",
|
|
72
58
|
"plugin",
|
|
73
59
|
"private",
|
|
@@ -98,9 +84,22 @@ const libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(
|
|
|
98
84
|
export const isAllowedLibFacetRootFile = (filename: string) =>
|
|
99
85
|
libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
|
|
100
86
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
//* What a Capacitor app kept in its root. The native runtime generates its projects under `.akan/native/<target>`, so
|
|
88
|
+
//* these are named as leftovers instead of as unknown entries.
|
|
89
|
+
const retiredAppRootEntries = new Set([
|
|
90
|
+
"android",
|
|
91
|
+
"ios",
|
|
92
|
+
"mobile",
|
|
93
|
+
"capacitor.config.ts",
|
|
94
|
+
"capacitor.config.js",
|
|
95
|
+
"capacitor.config.json",
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
export const rootEntryHintOf = (type: SysType, name: string) =>
|
|
99
|
+
type === "app" && retiredAppRootEntries.has(name)
|
|
100
|
+
? "a Capacitor-era entry; the native runtime generates its projects under .akan/native/<target>, so delete it"
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
// scanSync reads roots through `Bun.Glob("*")`, which skips dotfiles, so doctor skips them the same way.
|
|
105
104
|
export const isScannedRootEntry = (type: SysType, name: string) =>
|
|
106
105
|
!name.startsWith(".") || rootAllowedDirs[type].has(name);
|
package/abstractCompactor.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { Logger } from "akanjs/common";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { AbstractDoc, type AbstractKind } from "./abstractDoc";
|
|
4
|
-
import { AiSession } from "./aiEditor";
|
|
5
|
-
import type { SysExecutor } from "./executors";
|
|
6
|
-
|
|
7
|
-
export interface AbstractCompactOptions {
|
|
8
|
-
module?: string | null;
|
|
9
|
-
minLines?: number;
|
|
10
|
-
interactive?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AbstractCompactReport {
|
|
14
|
-
path: string;
|
|
15
|
-
beforeLines: number;
|
|
16
|
-
afterLines: number;
|
|
17
|
-
status: "compacted" | "unchanged" | "failed";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Rewrites bloated `*.abstract.md` files down to the invariants their source files cannot show. */
|
|
21
|
-
export class AbstractCompactor {
|
|
22
|
-
static readonly #reviewRequest = `Review the compacted file you just wrote against the original.
|
|
23
|
-
- Did you drop an invariant, a security reason, or a workflow step that the source files cannot show by themselves? Restore it.
|
|
24
|
-
- Did you keep anything that only restates code, scaffold wording, or history? Remove it.
|
|
25
|
-
- Is it in the same language as the original, within the line budget, and one short line per bullet?
|
|
26
|
-
Respond with exactly one \`\`\`markdown code block holding the corrected file and nothing else. If nothing needs to change, repeat the file unchanged.`;
|
|
27
|
-
|
|
28
|
-
#sys: SysExecutor;
|
|
29
|
-
#minLines: number;
|
|
30
|
-
#interactive: boolean;
|
|
31
|
-
constructor(
|
|
32
|
-
sys: SysExecutor,
|
|
33
|
-
{ minLines = AbstractDoc.compactMinLines, interactive = false }: AbstractCompactOptions = {},
|
|
34
|
-
) {
|
|
35
|
-
this.#sys = sys;
|
|
36
|
-
this.#minLines = minLines;
|
|
37
|
-
this.#interactive = interactive;
|
|
38
|
-
}
|
|
39
|
-
async compactAll({ module }: { module?: string | null } = {}) {
|
|
40
|
-
const docs = await AbstractDoc.findAll(this.#sys, { module });
|
|
41
|
-
const targets = docs.filter((doc) => doc.lineCount > this.#minLines);
|
|
42
|
-
const reports: AbstractCompactReport[] = [];
|
|
43
|
-
// One file at a time: each doc gets its own AI session, and parallel runs would interleave the
|
|
44
|
-
// streamed response with each other and with the interactive confirm prompt.
|
|
45
|
-
for (const doc of targets) reports.push(await this.compact(doc));
|
|
46
|
-
return { scanned: docs.length, reports };
|
|
47
|
-
}
|
|
48
|
-
async compact(doc: AbstractDoc): Promise<AbstractCompactReport> {
|
|
49
|
-
const session = new AiSession("compactAbstract", {
|
|
50
|
-
workspace: this.#sys.workspace,
|
|
51
|
-
cacheKey: `${this.#sys.name}-${doc.moduleName}`,
|
|
52
|
-
});
|
|
53
|
-
const approve = !this.#interactive;
|
|
54
|
-
const compacted = await session.editMarkdown(this.#request(doc), { approve });
|
|
55
|
-
const reviewed = await session.editMarkdown(AbstractCompactor.#reviewRequest, { approve });
|
|
56
|
-
// The review answers in prose when it finds nothing to fix, so the first compaction is the fallback.
|
|
57
|
-
const next = [reviewed, compacted]
|
|
58
|
-
.map((candidate) => candidate.trim())
|
|
59
|
-
.find((candidate) => doc.canReplaceWith(candidate));
|
|
60
|
-
const beforeLines = doc.lineCount;
|
|
61
|
-
if (!next) {
|
|
62
|
-
Logger.rawLog(chalk.yellow(`${doc.path}: the editor returned no shorter abstract, keeping the current file`));
|
|
63
|
-
return { path: doc.path, beforeLines, afterLines: beforeLines, status: "failed" };
|
|
64
|
-
}
|
|
65
|
-
if (next === doc.content.trim())
|
|
66
|
-
return { path: doc.path, beforeLines, afterLines: beforeLines, status: "unchanged" };
|
|
67
|
-
const content = `${next}\n`;
|
|
68
|
-
await this.#sys.writeFile(doc.path, content);
|
|
69
|
-
return { path: doc.path, beforeLines, afterLines: AbstractDoc.lineCountOf(content), status: "compacted" };
|
|
70
|
-
}
|
|
71
|
-
#targetShape(kind: AbstractKind) {
|
|
72
|
-
if (kind === "other")
|
|
73
|
-
return " - keep the headings that still carry information and drop the rest; do not impose a new structure";
|
|
74
|
-
return [
|
|
75
|
-
" - `# <name> Abstract` title line",
|
|
76
|
-
" - one declarative sentence naming what this module owns",
|
|
77
|
-
" - `## Rules` โ two to five bullets, each an invariant the code cannot show by itself",
|
|
78
|
-
" - optional `## Workflow` โ the lifecycle as an arrow chain (`draft -> signed -> active`) or a few bullets",
|
|
79
|
-
].join("\n");
|
|
80
|
-
}
|
|
81
|
-
#request(doc: AbstractDoc) {
|
|
82
|
-
return `You are compacting an Akan.js abstract file.
|
|
83
|
-
|
|
84
|
-
An abstract is the summary a coding agent reads before changing a module. Repeated edits have made this one
|
|
85
|
-
bloated, and it has to shrink back to what the source files cannot show by themselves.
|
|
86
|
-
|
|
87
|
-
# Target file
|
|
88
|
-
${this.#sys.type}s/${this.#sys.name}/${doc.path} (${doc.kind} module "${doc.moduleName}", ${doc.lineCount} lines)
|
|
89
|
-
|
|
90
|
-
# Source files in the same folder
|
|
91
|
-
${doc.siblingFiles.join(", ") || "none"}
|
|
92
|
-
|
|
93
|
-
# Current content
|
|
94
|
-
\`\`\`markdown
|
|
95
|
-
${doc.content}
|
|
96
|
-
\`\`\`
|
|
97
|
-
|
|
98
|
-
# Rules for the compacted file
|
|
99
|
-
- Write it in the SAME language as the current content. Never translate.
|
|
100
|
-
- Keep every invariant, constraint, security reason, and workflow step that the source files cannot show by
|
|
101
|
-
themselves. Merge duplicates instead of dropping either one.
|
|
102
|
-
- Delete what the code already states: field lists, types, labels, signatures, file lists, and imports.
|
|
103
|
-
- Delete scaffold and placeholder wording, changelog or migration history, TODOs, and any instruction about
|
|
104
|
-
reading or updating this file.
|
|
105
|
-
- Never invent a rule that the current content does not state.
|
|
106
|
-
- Keep every bullet to one short line, and stay under ${AbstractDoc.compactMinLines} lines in total.
|
|
107
|
-
- Shape:
|
|
108
|
-
${this.#targetShape(doc.kind)}
|
|
109
|
-
|
|
110
|
-
Respond with exactly one \`\`\`markdown code block holding the whole new file and nothing else.`;
|
|
111
|
-
}
|
|
112
|
-
}
|
package/aiEditor.test.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { parseTypescriptFileBlocks, preserveTypescriptResponseContent } from "./aiEditor";
|
|
3
|
-
|
|
4
|
-
describe("parseTypescriptFileBlocks", () => {
|
|
5
|
-
test("parses TypeScript file blocks with common fence variants", () => {
|
|
6
|
-
const writes = parseTypescriptFileBlocks(`
|
|
7
|
-
\`\`\`ts
|
|
8
|
-
|
|
9
|
-
// File: lib/car/car.constant.ts
|
|
10
|
-
|
|
11
|
-
export const car = "car";
|
|
12
|
-
\`\`\`
|
|
13
|
-
|
|
14
|
-
\`\`\`tsx
|
|
15
|
-
// File: lib/car/Car.Unit.tsx
|
|
16
|
-
export const CarUnit = () => null;
|
|
17
|
-
\`\`\`
|
|
18
|
-
`);
|
|
19
|
-
|
|
20
|
-
expect(writes).toEqual([
|
|
21
|
-
{
|
|
22
|
-
filePath: "lib/car/car.constant.ts",
|
|
23
|
-
content: 'export const car = "car";',
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
filePath: "lib/car/Car.Unit.tsx",
|
|
27
|
-
content: "export const CarUnit = () => null;",
|
|
28
|
-
},
|
|
29
|
-
]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("keeps previous code response when validation responds with prose only", () => {
|
|
33
|
-
const previousContent = `
|
|
34
|
-
\`\`\`typescript
|
|
35
|
-
// File: lib/car/car.constant.ts
|
|
36
|
-
export const car = "car";
|
|
37
|
-
\`\`\`
|
|
38
|
-
`;
|
|
39
|
-
const nextContent = "The generated file meets all specified requirements. No rewrite is necessary.";
|
|
40
|
-
|
|
41
|
-
expect(preserveTypescriptResponseContent(previousContent, nextContent)).toBe(previousContent);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("uses next code response when validation rewrites with parseable files", () => {
|
|
45
|
-
const previousContent = `
|
|
46
|
-
\`\`\`typescript
|
|
47
|
-
// File: lib/car/car.constant.ts
|
|
48
|
-
export const car = "car";
|
|
49
|
-
\`\`\`
|
|
50
|
-
`;
|
|
51
|
-
const nextContent = `
|
|
52
|
-
\`\`\`typescript
|
|
53
|
-
// File: lib/car/car.constant.ts
|
|
54
|
-
export const car = "updated";
|
|
55
|
-
\`\`\`
|
|
56
|
-
`;
|
|
57
|
-
|
|
58
|
-
expect(preserveTypescriptResponseContent(previousContent, nextContent)).toBe(nextContent);
|
|
59
|
-
});
|
|
60
|
-
});
|
package/aiEditor.ts
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { input, select } from "@inquirer/prompts";
|
|
2
|
-
import {
|
|
3
|
-
AIMessage,
|
|
4
|
-
type BaseMessage,
|
|
5
|
-
HumanMessage,
|
|
6
|
-
mapChatMessagesToStoredMessages,
|
|
7
|
-
mapStoredMessagesToChatMessages,
|
|
8
|
-
type StoredMessage,
|
|
9
|
-
} from "@langchain/core/messages";
|
|
10
|
-
import { ChatDeepSeek } from "@langchain/deepseek";
|
|
11
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
12
|
-
import { Logger } from "akanjs/common";
|
|
13
|
-
import chalk from "chalk";
|
|
14
|
-
|
|
15
|
-
import { GlobalConfig } from "./cloud";
|
|
16
|
-
import type { Executor, WorkspaceExecutor } from "./executors";
|
|
17
|
-
import { Spinner } from "./spinner";
|
|
18
|
-
import type { FileContent } from "./types";
|
|
19
|
-
|
|
20
|
-
const MAX_ASK_TRY = 300;
|
|
21
|
-
|
|
22
|
-
const deepSeekLlmModels = ["deepseek-v4-flash", "deepseek-v4-pro"] as const;
|
|
23
|
-
|
|
24
|
-
const openAiLlmModels = ["gpt-5.5"] as const;
|
|
25
|
-
|
|
26
|
-
export const supportedLlmModels = [...deepSeekLlmModels, ...openAiLlmModels] as const;
|
|
27
|
-
export type SupportedLlmModel = (typeof supportedLlmModels)[number];
|
|
28
|
-
type OpenAiLlmModel = (typeof openAiLlmModels)[number];
|
|
29
|
-
|
|
30
|
-
const isOpenAiLlmModel = (model: SupportedLlmModel): model is OpenAiLlmModel =>
|
|
31
|
-
openAiLlmModels.includes(model as OpenAiLlmModel);
|
|
32
|
-
|
|
33
|
-
interface EditOptions {
|
|
34
|
-
onReasoning?: (reasoning: string) => void;
|
|
35
|
-
onChunk?: (chunk: string) => void;
|
|
36
|
-
maxTry?: number;
|
|
37
|
-
validate?: string[];
|
|
38
|
-
approve?: boolean;
|
|
39
|
-
fallbackToPreviousTypescript?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const parseTypescriptFileBlocks = (text: string): FileContent[] => {
|
|
43
|
-
const fileBlocks: FileContent[] = [];
|
|
44
|
-
const codeBlockRegex = /```(?:typescript|ts|tsx)\s*\n([\s\S]*?)```/gi;
|
|
45
|
-
const filePathRegex = /^\s*\/\/\s*File:\s*(.+?)\s*$/im;
|
|
46
|
-
|
|
47
|
-
for (const codeBlock of text.matchAll(codeBlockRegex)) {
|
|
48
|
-
const content = codeBlock[1]?.trim();
|
|
49
|
-
if (!content) continue;
|
|
50
|
-
|
|
51
|
-
const filePath = filePathRegex.exec(content)?.[1]?.trim();
|
|
52
|
-
if (!filePath) continue;
|
|
53
|
-
|
|
54
|
-
fileBlocks.push({
|
|
55
|
-
filePath,
|
|
56
|
-
content: content.replace(filePathRegex, "").trim(),
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return fileBlocks;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export const preserveTypescriptResponseContent = (previousContent: string, nextContent: string) => {
|
|
64
|
-
const previousWrites = parseTypescriptFileBlocks(previousContent);
|
|
65
|
-
const nextWrites = parseTypescriptFileBlocks(nextContent);
|
|
66
|
-
if (previousWrites.length > 0 && nextWrites.length === 0) return previousContent;
|
|
67
|
-
return nextContent;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export class AiSession {
|
|
71
|
-
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
72
|
-
static #chat: ChatDeepSeek | ChatOpenAI | null = null;
|
|
73
|
-
static async init({ temperature = 0, useExisting = true }: { temperature?: number; useExisting?: boolean } = {}) {
|
|
74
|
-
if (useExisting) {
|
|
75
|
-
const llmConfig = await AiSession.getLlmConfig();
|
|
76
|
-
if (llmConfig) {
|
|
77
|
-
AiSession.#setChatModel(llmConfig.model, llmConfig.apiKey);
|
|
78
|
-
Logger.rawLog(chalk.dim(`๐คakan editor uses existing LLM config (${llmConfig.model})`));
|
|
79
|
-
return AiSession;
|
|
80
|
-
}
|
|
81
|
-
} else Logger.rawLog(chalk.yellow("๐คakan-editor is not initialized. LLM configuration should be set first."));
|
|
82
|
-
|
|
83
|
-
const llmConfig = await AiSession.#requestLlmConfig();
|
|
84
|
-
const { model, apiKey } = llmConfig;
|
|
85
|
-
|
|
86
|
-
await AiSession.#validateApiKey(model, apiKey);
|
|
87
|
-
const session = AiSession.#setChatModel(model, apiKey, { temperature });
|
|
88
|
-
await session.setLlmConfig({ model, apiKey });
|
|
89
|
-
return session;
|
|
90
|
-
}
|
|
91
|
-
static #setChatModel(model: SupportedLlmModel, apiKey: string, { temperature = 0 }: { temperature?: number } = {}) {
|
|
92
|
-
AiSession.#chat = AiSession.#createChatModel(model, apiKey, {
|
|
93
|
-
temperature,
|
|
94
|
-
streaming: true,
|
|
95
|
-
});
|
|
96
|
-
return AiSession;
|
|
97
|
-
}
|
|
98
|
-
static #createChatModel(
|
|
99
|
-
model: SupportedLlmModel,
|
|
100
|
-
apiKey: string,
|
|
101
|
-
{ temperature = 0, streaming = false }: { temperature?: number; streaming?: boolean } = {},
|
|
102
|
-
) {
|
|
103
|
-
if (isOpenAiLlmModel(model))
|
|
104
|
-
return new ChatOpenAI({
|
|
105
|
-
modelName: model,
|
|
106
|
-
temperature,
|
|
107
|
-
streaming,
|
|
108
|
-
openAIApiKey: apiKey,
|
|
109
|
-
});
|
|
110
|
-
return new ChatDeepSeek({
|
|
111
|
-
modelName: model,
|
|
112
|
-
temperature,
|
|
113
|
-
streaming,
|
|
114
|
-
apiKey,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
static async getLlmConfig() {
|
|
118
|
-
return await GlobalConfig.getLlmConfig();
|
|
119
|
-
}
|
|
120
|
-
static async setLlmConfig(llmConfig: { model: SupportedLlmModel; apiKey: string } | null) {
|
|
121
|
-
await GlobalConfig.setLlmConfig(llmConfig);
|
|
122
|
-
return AiSession;
|
|
123
|
-
}
|
|
124
|
-
static async #requestLlmConfig() {
|
|
125
|
-
const model = await select<SupportedLlmModel>({
|
|
126
|
-
message: "Select a LLM model",
|
|
127
|
-
choices: supportedLlmModels,
|
|
128
|
-
});
|
|
129
|
-
const apiKey = await input({ message: "Enter your API key" });
|
|
130
|
-
return { model, apiKey };
|
|
131
|
-
}
|
|
132
|
-
static async #validateApiKey(modelName: SupportedLlmModel, apiKey: string) {
|
|
133
|
-
const spinner = new Spinner("Validating LLM API key...", {
|
|
134
|
-
prefix: `๐คakan-editor`,
|
|
135
|
-
}).start();
|
|
136
|
-
const chat = AiSession.#createChatModel(modelName, apiKey);
|
|
137
|
-
try {
|
|
138
|
-
await chat.invoke("Hi, and just say 'ok'");
|
|
139
|
-
spinner.succeed("LLM API key is valid");
|
|
140
|
-
return true;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
spinner.fail(
|
|
143
|
-
chalk.red(
|
|
144
|
-
`LLM API key is invalid. Please check your API key and try again. You can set it again by running "akan set-llm" or reset by running "akan reset-llm"`,
|
|
145
|
-
),
|
|
146
|
-
);
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
static async clearCache(workspaceRoot: string) {
|
|
151
|
-
const cacheDir = `${workspaceRoot}/${AiSession.#cacheDir}`;
|
|
152
|
-
await Bun.$`rm -rf ${cacheDir}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
messageHistory: BaseMessage[] = [];
|
|
156
|
-
readonly sessionKey: string;
|
|
157
|
-
isCacheLoaded: boolean = false;
|
|
158
|
-
workspace: WorkspaceExecutor;
|
|
159
|
-
constructor(
|
|
160
|
-
type: string,
|
|
161
|
-
{
|
|
162
|
-
workspace,
|
|
163
|
-
cacheKey,
|
|
164
|
-
isContinued,
|
|
165
|
-
}: {
|
|
166
|
-
workspace: WorkspaceExecutor;
|
|
167
|
-
cacheKey?: string;
|
|
168
|
-
isContinued?: boolean;
|
|
169
|
-
},
|
|
170
|
-
) {
|
|
171
|
-
this.workspace = workspace;
|
|
172
|
-
this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
|
|
173
|
-
if (isContinued) this.#cacheLoadPromise = this.#loadCache();
|
|
174
|
-
}
|
|
175
|
-
#cacheLoadPromise: Promise<void> | null = null;
|
|
176
|
-
async #loadCache() {
|
|
177
|
-
const cacheFile = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
178
|
-
const isCacheExists = await this.workspace.exists(cacheFile);
|
|
179
|
-
if (isCacheExists)
|
|
180
|
-
this.messageHistory = mapStoredMessagesToChatMessages(
|
|
181
|
-
(await this.workspace.readJson(cacheFile)) as StoredMessage[],
|
|
182
|
-
);
|
|
183
|
-
else this.messageHistory = [];
|
|
184
|
-
this.isCacheLoaded = isCacheExists;
|
|
185
|
-
}
|
|
186
|
-
async #saveCache() {
|
|
187
|
-
const cacheFilePath = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
188
|
-
await this.workspace.writeJson(cacheFilePath, mapChatMessagesToStoredMessages(this.messageHistory));
|
|
189
|
-
}
|
|
190
|
-
async ask(
|
|
191
|
-
question: string,
|
|
192
|
-
{
|
|
193
|
-
onReasoning = (reasoning: string) => {
|
|
194
|
-
Logger.raw(chalk.dim(reasoning));
|
|
195
|
-
},
|
|
196
|
-
onChunk = (chunk: string) => {
|
|
197
|
-
Logger.raw(chunk);
|
|
198
|
-
},
|
|
199
|
-
}: EditOptions = {},
|
|
200
|
-
): Promise<{ content: string; messageHistory: BaseMessage[] }> {
|
|
201
|
-
if (!AiSession.#chat) await AiSession.init();
|
|
202
|
-
if (this.#cacheLoadPromise) await this.#cacheLoadPromise;
|
|
203
|
-
|
|
204
|
-
if (!AiSession.#chat) throw new Error("Failed to initialize the AI session");
|
|
205
|
-
const loader = new Spinner(`${AiSession.#chat.model} is thinking...`, {
|
|
206
|
-
prefix: `๐คakan-editor`,
|
|
207
|
-
}).start();
|
|
208
|
-
try {
|
|
209
|
-
const humanMessage = new HumanMessage(question);
|
|
210
|
-
this.messageHistory.push(humanMessage);
|
|
211
|
-
const stream = await AiSession.#chat.stream(this.messageHistory);
|
|
212
|
-
let reasoningResponse = "",
|
|
213
|
-
fullResponse = "";
|
|
214
|
-
for await (const chunk of stream) {
|
|
215
|
-
if (loader.isSpinning()) loader.succeed(`${AiSession.#chat.model} responded`);
|
|
216
|
-
|
|
217
|
-
if (!fullResponse.length) {
|
|
218
|
-
const reasoningContent = (chunk.additional_kwargs as { reasoning_content?: string }).reasoning_content ?? "";
|
|
219
|
-
if (reasoningContent.length) {
|
|
220
|
-
reasoningResponse += reasoningContent;
|
|
221
|
-
onReasoning(reasoningContent);
|
|
222
|
-
continue;
|
|
223
|
-
} else if (chunk.content.length) {
|
|
224
|
-
reasoningResponse += "\n";
|
|
225
|
-
onReasoning(reasoningResponse);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const content = chunk.content;
|
|
230
|
-
if (typeof content === "string") {
|
|
231
|
-
fullResponse += content;
|
|
232
|
-
onChunk(content); // Send individual chunks to callback
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
fullResponse += "\n";
|
|
236
|
-
onChunk("\n");
|
|
237
|
-
this.messageHistory.push(new AIMessage(fullResponse));
|
|
238
|
-
return { content: fullResponse, messageHistory: this.messageHistory };
|
|
239
|
-
} catch {
|
|
240
|
-
loader.fail(`${AiSession.#chat.model} failed to respond`);
|
|
241
|
-
throw new Error("Failed to stream response");
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
async edit(
|
|
245
|
-
question: string,
|
|
246
|
-
{ onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve, fallbackToPreviousTypescript }: EditOptions = {},
|
|
247
|
-
) {
|
|
248
|
-
for (let tryCount = 0; tryCount < maxTry; tryCount++) {
|
|
249
|
-
let response = await this.ask(question, { onChunk, onReasoning });
|
|
250
|
-
if (validate?.length && tryCount === 0) {
|
|
251
|
-
const validateQuestion = `Double check if the response meets the requirements and conditions, and follow the instructions. If not, rewrite it.
|
|
252
|
-
${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
253
|
-
const validateResponse = await this.ask(validateQuestion, {
|
|
254
|
-
onChunk,
|
|
255
|
-
onReasoning,
|
|
256
|
-
});
|
|
257
|
-
response = {
|
|
258
|
-
...validateResponse,
|
|
259
|
-
content: fallbackToPreviousTypescript
|
|
260
|
-
? preserveTypescriptResponseContent(response.content, validateResponse.content)
|
|
261
|
-
: validateResponse.content,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
const isConfirmed = approve
|
|
265
|
-
? true
|
|
266
|
-
: await select({
|
|
267
|
-
message: "Do you want to edit the response?",
|
|
268
|
-
choices: [
|
|
269
|
-
{ name: "โ
Yes, confirm and apply this result", value: true },
|
|
270
|
-
{ name: "๐ No, I want to edit it more", value: false },
|
|
271
|
-
],
|
|
272
|
-
});
|
|
273
|
-
if (isConfirmed) {
|
|
274
|
-
await this.#saveCache();
|
|
275
|
-
return response.content;
|
|
276
|
-
}
|
|
277
|
-
question = await input({ message: "What do you want to change?" });
|
|
278
|
-
tryCount++;
|
|
279
|
-
}
|
|
280
|
-
throw new Error("Failed to edit");
|
|
281
|
-
}
|
|
282
|
-
async editTypescript(question: string, options: EditOptions = {}) {
|
|
283
|
-
const content = await this.edit(question, options);
|
|
284
|
-
return this.#getTypescriptCode(content);
|
|
285
|
-
}
|
|
286
|
-
#getTypescriptCode(content: string) {
|
|
287
|
-
//! will be deprecated
|
|
288
|
-
const code = /```(typescript|tsx)([\s\S]*?)```/.exec(content);
|
|
289
|
-
// 2๋ฒ์งธ๋ก ํด์ผ ๋ฐํ๋๋๋ฐ ๋ชจ๋ฅด๊ฒ์ ์๋ฌดํผ ์ผ๋จ ์ด๋ ๊ฒ ํจ.
|
|
290
|
-
|
|
291
|
-
return code?.[2] ?? content;
|
|
292
|
-
// return code ? code[1] : content;
|
|
293
|
-
}
|
|
294
|
-
addToolMessgaes(messages: { type: string; content: string }[]) {
|
|
295
|
-
// const toolMessages = messages.map(
|
|
296
|
-
// (message) => new ToolMessage({ content: message.content, tool_call_id: message.type })
|
|
297
|
-
// );
|
|
298
|
-
const toolMessages = messages.map((message) => new HumanMessage(message.content));
|
|
299
|
-
this.messageHistory.push(...toolMessages);
|
|
300
|
-
return this;
|
|
301
|
-
}
|
|
302
|
-
async writeTypescripts(question: string, executor: Executor, options: EditOptions = {}) {
|
|
303
|
-
const content = await this.edit(question, {
|
|
304
|
-
...options,
|
|
305
|
-
fallbackToPreviousTypescript: true,
|
|
306
|
-
});
|
|
307
|
-
const writes = this.#getTypescriptCodes(content);
|
|
308
|
-
if (!writes.length)
|
|
309
|
-
throw new Error(
|
|
310
|
-
"No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.",
|
|
311
|
-
);
|
|
312
|
-
for (const write of writes) await executor.writeFile(write.filePath, write.content);
|
|
313
|
-
return await this.#tryFixTypescripts(writes, executor, options);
|
|
314
|
-
}
|
|
315
|
-
async #editTypescripts(question: string, options: EditOptions = {}, fallbackWrites?: FileContent[]) {
|
|
316
|
-
const content = await this.edit(question, {
|
|
317
|
-
...options,
|
|
318
|
-
fallbackToPreviousTypescript: true,
|
|
319
|
-
});
|
|
320
|
-
const writes = this.#getTypescriptCodes(content);
|
|
321
|
-
if (!writes.length && fallbackWrites?.length) return fallbackWrites;
|
|
322
|
-
if (!writes.length)
|
|
323
|
-
throw new Error(
|
|
324
|
-
"No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.",
|
|
325
|
-
);
|
|
326
|
-
return writes;
|
|
327
|
-
}
|
|
328
|
-
async #tryFixTypescripts(writes: FileContent[], executor: Executor, options: EditOptions = {}) {
|
|
329
|
-
const MAX_EDIT_TRY = 5;
|
|
330
|
-
for (let tryCount = 0; tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
331
|
-
const loader = new Spinner(`Type checking and linting...`, {
|
|
332
|
-
prefix: `๐คakan-editor`,
|
|
333
|
-
}).start();
|
|
334
|
-
const fileChecks = await Promise.all(
|
|
335
|
-
writes.map(async ({ filePath }) => {
|
|
336
|
-
const lintResult = await executor.lint(filePath, { fix: true });
|
|
337
|
-
const typeCheckResult = await executor.typeCheckAsync(filePath);
|
|
338
|
-
const hasTypeErrors = typeCheckResult.fileErrors.length > 0;
|
|
339
|
-
const hasLintErrors = lintResult.errors.length > 0;
|
|
340
|
-
const needFix = hasTypeErrors || hasLintErrors;
|
|
341
|
-
return { filePath, typeCheckResult, lintResult, needFix };
|
|
342
|
-
}),
|
|
343
|
-
);
|
|
344
|
-
const hasAnyFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
345
|
-
if (hasAnyFix) {
|
|
346
|
-
loader.fail("Type checking and linting has some errors, try to fix them");
|
|
347
|
-
fileChecks.forEach((fileCheck) => {
|
|
348
|
-
Logger.rawLog(
|
|
349
|
-
`TypeCheck Result \n${fileCheck.typeCheckResult.message}\nLint Result \n${fileCheck.lintResult.message}`,
|
|
350
|
-
);
|
|
351
|
-
this.addToolMessgaes([
|
|
352
|
-
{ type: "typescript", content: fileCheck.typeCheckResult.message },
|
|
353
|
-
{ type: "eslint", content: fileCheck.lintResult.message },
|
|
354
|
-
]);
|
|
355
|
-
});
|
|
356
|
-
writes = await this.#editTypescripts(
|
|
357
|
-
"Fix the typescript and eslint errors",
|
|
358
|
-
{
|
|
359
|
-
...options,
|
|
360
|
-
validate: undefined,
|
|
361
|
-
approve: true,
|
|
362
|
-
},
|
|
363
|
-
writes,
|
|
364
|
-
);
|
|
365
|
-
for (const write of writes) await executor.writeFile(write.filePath, write.content);
|
|
366
|
-
} else {
|
|
367
|
-
loader.succeed("Type checking and linting has no errors");
|
|
368
|
-
return writes;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
throw new Error("Failed to create scalar");
|
|
372
|
-
}
|
|
373
|
-
#getTypescriptCodes(text: string): FileContent[] {
|
|
374
|
-
return parseTypescriptFileBlocks(text);
|
|
375
|
-
}
|
|
376
|
-
async editMarkdown(request: string, options: EditOptions = {}) {
|
|
377
|
-
const content = await this.edit(request, options);
|
|
378
|
-
return this.#getMarkdownContent(content);
|
|
379
|
-
}
|
|
380
|
-
#getMarkdownContent(text: string) {
|
|
381
|
-
const searchText = "```markdown";
|
|
382
|
-
const firstIndex = text.indexOf("```markdown");
|
|
383
|
-
const lastIndex = text.lastIndexOf("```");
|
|
384
|
-
if (firstIndex === -1) return text;
|
|
385
|
-
else return text.slice(firstIndex + searchText.length, lastIndex).trim();
|
|
386
|
-
}
|
|
387
|
-
}
|