@akanjs/devkit 3.0.0-beta.19 → 3.0.0-beta.20
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/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +1 -4
- 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 +1 -3
- package/akanApp/akanApp.host.ts +64 -250
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +30 -39
- package/akanConfig/akanConfig.ts +40 -136
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.ts +25 -59
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +3 -19
- package/artifact/implicitRootLayout.ts +34 -91
- package/artifact/routeSeedIndex.ts +11 -44
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +4 -14
- package/capacitorApp.ts +86 -235
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +33 -57
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +3 -2
- package/cloud/globalConfig.ts +64 -31
- package/codeAgent/agent/AkanCodeServices.ts +15 -57
- package/codeAgent/agent/AkanContextFiles.ts +5 -28
- package/codeAgent/agent/CodeAgent.ts +33 -108
- package/codeAgent/agent/CodeAgentAsks.ts +7 -30
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
- package/codeAgent/agent/CodeAgentGate.ts +8 -34
- package/codeAgent/agent/CodeAgentProxy.ts +4 -13
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
- package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
- package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
- package/codeAgent/agent/CodeAgentUi.ts +2 -7
- package/codeAgent/agent/CodeMailbox.ts +7 -34
- package/codeAgent/agent/CodeSessionFork.ts +4 -19
- package/codeAgent/agent/CodeSessionIndex.ts +5 -31
- package/codeAgent/agent/SubagentPool.ts +48 -58
- package/codeAgent/agent/akanCodeModel.ts +5 -39
- package/codeAgent/agent/akanCodePaths.ts +4 -24
- package/codeAgent/agent/akanContextFiles.test.ts +0 -2
- package/codeAgent/agent/akanSystemPrompt.ts +1 -9
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +5 -28
- package/codeAgent/agent/codeMailbox.test.ts +0 -4
- package/codeAgent/agent/codeSessionFork.test.ts +0 -2
- package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
- package/codeAgent/agent/consoleToStderr.ts +2 -12
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +3 -18
- package/codeAgent/feedback/PreviewChrome.ts +5 -29
- package/codeAgent/feedback/PreviewFeedback.ts +2 -9
- package/codeAgent/feedback/PreviewView.ts +3 -15
- package/codeAgent/feedback/TurnFeedback.ts +2 -17
- package/codeAgent/feedback/VerifyFeedback.ts +1 -7
- package/codeAgent/feedback/previewChrome.test.ts +1 -1
- package/codeAgent/index.ts +2 -10
- package/codeAgent/skills/akan-code/SKILL.md +12 -4
- package/codeAgent/tools/AkanCli.ts +4 -16
- package/codeAgent/tools/AkanCodePlugins.ts +21 -43
- package/codeAgent/tools/AkanEditScope.ts +2 -12
- package/codeAgent/tools/AkanEnvKeys.ts +3 -11
- package/codeAgent/tools/AkanToolPack.ts +6 -28
- package/codeAgent/tools/AkanVerifier.ts +1 -8
- package/codeAgent/tools/AskToolPack.ts +3 -15
- package/codeAgent/tools/MailToolPack.ts +0 -9
- package/codeAgent/tools/McpClient.ts +3 -24
- package/codeAgent/tools/McpOAuth.ts +6 -41
- package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
- package/codeAgent/tools/McpServerConfig.ts +7 -26
- package/codeAgent/tools/McpSignIn.ts +46 -49
- package/codeAgent/tools/McpTokenStore.ts +6 -17
- package/codeAgent/tools/McpToolPack.ts +107 -35
- package/codeAgent/tools/SessionToolPack.ts +1 -11
- package/codeAgent/tools/WebToolPack.ts +1 -7
- package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
- package/codeAgent/tools/askToolPack.test.ts +0 -3
- package/codeAgent/tools/mcpOAuth.test.ts +315 -27
- package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
- 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 +44 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +1 -9
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +66 -144
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +27 -78
- package/devkitUtils.test.ts +47 -19
- package/executors.test.ts +114 -60
- package/executors.ts +165 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +1 -1
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +14 -38
- package/frontendBuild/autoImportSync.ts +15 -67
- package/frontendBuild/buildRouteClient.test.ts +10 -36
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +5 -18
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +5 -13
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +27 -52
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +7 -31
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +7 -47
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- 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 +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- 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/devWatchBatch.test.ts +14 -18
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
- package/index.ts +3 -15
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +9 -29
- 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/gritRules.test.ts +6 -23
- package/linter.test.ts +1 -2
- package/linter.ts +22 -115
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +2 -2
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +3 -14
- package/platformTest/IdleWatchedProcess.test.ts +5 -13
- package/platformTest/IdleWatchedProcess.ts +1 -5
- package/platformTest/LinuxDockerTarget.ts +20 -35
- package/platformTest/PlatformTestRun.test.ts +5 -9
- package/platformTest/PlatformTestTarget.ts +24 -2
- package/platformTest/TestSnapshot.test.ts +6 -14
- package/platformTest/TestSnapshot.ts +1 -5
- package/platformTest/WindowsSshTarget.ts +6 -22
- package/prompter.ts +3 -6
- package/qualityScanner.test.ts +6 -24
- package/qualityScanner.ts +196 -284
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.ts +7 -30
- package/scanInfo.ts +82 -214
- 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/subspace.test.ts +6 -25
- package/subspace.ts +31 -109
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +49 -34
- 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 +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +85 -146
- 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/source.ts +33 -56
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- 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/workflow/moduleIndex.ts
CHANGED
|
@@ -4,6 +4,15 @@ import ts from "typescript";
|
|
|
4
4
|
import type { AkanModuleContext } from "../akanContext";
|
|
5
5
|
import type { Workspace } from "../commandDecorators";
|
|
6
6
|
import { moduleComponentName, moduleSourcePaths } from "./source";
|
|
7
|
+
import {
|
|
8
|
+
callExpressionName,
|
|
9
|
+
expressionName,
|
|
10
|
+
firstObjectReturnedByArrow,
|
|
11
|
+
heritageCall,
|
|
12
|
+
nodeName,
|
|
13
|
+
propertyName,
|
|
14
|
+
sourceFileFor,
|
|
15
|
+
} from "./sourceAst";
|
|
7
16
|
import type {
|
|
8
17
|
AkanConstantIndex,
|
|
9
18
|
AkanDictionaryIndex,
|
|
@@ -52,9 +61,6 @@ const fileExists = async (filePath: string) => {
|
|
|
52
61
|
}
|
|
53
62
|
};
|
|
54
63
|
|
|
55
|
-
const sourceFileFor = (filePath: string, content: string) =>
|
|
56
|
-
ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
57
|
-
|
|
58
64
|
const parseDiagnosticsFor = (source: ts.SourceFile, filePath: string): WorkflowDiagnostic[] => {
|
|
59
65
|
const parseDiagnostics =
|
|
60
66
|
(source as ts.SourceFile & { parseDiagnostics?: readonly ts.DiagnosticWithLocation[] }).parseDiagnostics ?? [];
|
|
@@ -83,25 +89,6 @@ const spanFor = (source: ts.SourceFile, file: string, node: ts.Node): AkanSource
|
|
|
83
89
|
};
|
|
84
90
|
};
|
|
85
91
|
|
|
86
|
-
const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
|
|
87
|
-
if (!node) return null;
|
|
88
|
-
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
89
|
-
return null;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const propertyName = (node: ts.ObjectLiteralElementLike) =>
|
|
93
|
-
ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
|
|
94
|
-
? nodeName(node.name)
|
|
95
|
-
: null;
|
|
96
|
-
|
|
97
|
-
const expressionName = (expression: ts.Expression): string | null => {
|
|
98
|
-
if (ts.isIdentifier(expression)) return expression.text;
|
|
99
|
-
if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
|
|
100
|
-
if (ts.isCallExpression(expression)) return expressionName(expression.expression);
|
|
101
|
-
if (ts.isAsExpression(expression)) return expressionName(expression.expression);
|
|
102
|
-
return null;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
92
|
const expressionSummary = (expression: ts.Expression): string => {
|
|
106
93
|
if (ts.isAsExpression(expression)) return expressionSummary(expression.expression);
|
|
107
94
|
if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression))
|
|
@@ -151,30 +138,6 @@ const fieldsFromObject = (
|
|
|
151
138
|
})
|
|
152
139
|
.filter((field): field is AkanFieldOutline => field !== null);
|
|
153
140
|
|
|
154
|
-
const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
|
|
155
|
-
if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
|
|
156
|
-
if (ts.isObjectLiteralExpression(node.body)) return node.body;
|
|
157
|
-
if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
|
|
158
|
-
return node.body.expression;
|
|
159
|
-
}
|
|
160
|
-
if (!ts.isBlock(node.body)) return null;
|
|
161
|
-
for (const statement of node.body.statements) {
|
|
162
|
-
if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
|
|
163
|
-
return statement.expression;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return null;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const isViaCall = (expression: ts.Expression) =>
|
|
170
|
-
ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
|
|
171
|
-
|
|
172
|
-
const heritageCall = (node: ts.ClassDeclaration) => {
|
|
173
|
-
const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
|
|
174
|
-
const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
|
|
175
|
-
return expression && ts.isCallExpression(expression) ? expression : null;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
141
|
interface ParsedConstantIndex {
|
|
179
142
|
index: AkanConstantIndex;
|
|
180
143
|
diagnostics: WorkflowDiagnostic[];
|
|
@@ -297,9 +260,6 @@ const parseConstantIndex = (filePath: string, content: string, moduleClassName:
|
|
|
297
260
|
};
|
|
298
261
|
};
|
|
299
262
|
|
|
300
|
-
const callExpressionName = (node: ts.CallExpression) =>
|
|
301
|
-
ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
|
|
302
|
-
|
|
303
263
|
const parseDictionaryIndex = (filePath: string, content: string, moduleClassName: string): ParsedDictionaryIndex => {
|
|
304
264
|
const source = sourceFileFor(filePath, content);
|
|
305
265
|
const diagnostics = parseDiagnosticsFor(source, filePath);
|
package/workflow/plan.ts
CHANGED
|
@@ -206,31 +206,24 @@ const createAddFieldRecommendations = (inputs: Record<string, WorkflowInputValue
|
|
|
206
206
|
},
|
|
207
207
|
]
|
|
208
208
|
: []),
|
|
209
|
-
|
|
210
|
-
?
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
|
|
227
|
-
kind: "placement" as const,
|
|
228
|
-
target: paths.constant,
|
|
229
|
-
confidence: "high" as const,
|
|
230
|
-
message: `Add ${field} to Light${capitalize(module)} projection fields.`,
|
|
231
|
-
},
|
|
232
|
-
]
|
|
233
|
-
: []),
|
|
209
|
+
includeInLight
|
|
210
|
+
? {
|
|
211
|
+
code: "add-field-light-projection",
|
|
212
|
+
kind: "placement",
|
|
213
|
+
target: paths.constant,
|
|
214
|
+
confidence: "high",
|
|
215
|
+
message: `Add ${field} to Light${capitalize(module)} projection fields.`,
|
|
216
|
+
}
|
|
217
|
+
: {
|
|
218
|
+
code: "add-field-light-projection-choice",
|
|
219
|
+
kind: "manual-action",
|
|
220
|
+
target: paths.constant,
|
|
221
|
+
action: `Pass includeInLight=true when users should see ${field} in list/card data. Without it, the field can exist on ${capitalize(
|
|
222
|
+
module,
|
|
223
|
+
)}Input but stay absent from Light${capitalize(module)} projections.`,
|
|
224
|
+
confidence: "medium",
|
|
225
|
+
message: `${module}.${field} is not selected for list/card projection data.`,
|
|
226
|
+
},
|
|
234
227
|
...(surfaces && !templateRequested
|
|
235
228
|
? [
|
|
236
229
|
{
|
package/workflow/render.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
PrimitiveFormat,
|
|
3
|
+
PrimitiveWriteReport,
|
|
2
4
|
RepairReport,
|
|
3
5
|
WorkflowApplyReport,
|
|
6
|
+
WorkflowDiagnostic,
|
|
4
7
|
WorkflowFormat,
|
|
5
8
|
WorkflowPlan,
|
|
6
9
|
WorkflowRunArtifact,
|
|
@@ -9,6 +12,23 @@ import type {
|
|
|
9
12
|
} from "./types";
|
|
10
13
|
import { jsonText } from "./utils";
|
|
11
14
|
|
|
15
|
+
const listOr = <T>(items: readonly T[] | undefined, render: (item: T) => string, empty = "- none") =>
|
|
16
|
+
items?.length ? items.map(render) : [empty];
|
|
17
|
+
|
|
18
|
+
const renderDiagnostic = (diagnostic: WorkflowDiagnostic) =>
|
|
19
|
+
`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
|
|
20
|
+
|
|
21
|
+
const renderCommand = (entry: { command: string; reason: string }) => `- \`${entry.command}\`: ${entry.reason}`;
|
|
22
|
+
|
|
23
|
+
const renderFile = (file: { action: string; path: string; reason: string }) =>
|
|
24
|
+
`- \`${file.action}\` ${file.path}: ${file.reason}`;
|
|
25
|
+
|
|
26
|
+
const renderRecommendation = (recommendation: WorkflowApplyReport["recommendations"][number]) => {
|
|
27
|
+
const target = recommendation.target ? ` ${recommendation.target}` : "";
|
|
28
|
+
const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
|
|
29
|
+
return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
|
|
30
|
+
};
|
|
31
|
+
|
|
12
32
|
export const renderWorkflowList = (specs: readonly WorkflowSpec[]) =>
|
|
13
33
|
[
|
|
14
34
|
"# Akan Workflows",
|
|
@@ -41,7 +61,7 @@ export const renderWorkflowExplain = (spec: WorkflowSpec) =>
|
|
|
41
61
|
...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
42
62
|
"",
|
|
43
63
|
"## Validation",
|
|
44
|
-
...spec.validation.map(
|
|
64
|
+
...spec.validation.map(renderCommand),
|
|
45
65
|
"",
|
|
46
66
|
].join("\n");
|
|
47
67
|
|
|
@@ -71,43 +91,16 @@ export const renderWorkflowPlan = (plan: WorkflowPlan) =>
|
|
|
71
91
|
}),
|
|
72
92
|
"",
|
|
73
93
|
"## Validation",
|
|
74
|
-
...plan.validation.map(
|
|
94
|
+
...plan.validation.map(renderCommand),
|
|
75
95
|
"",
|
|
76
96
|
"## Diagnostics",
|
|
77
|
-
...(plan.diagnostics
|
|
78
|
-
? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
79
|
-
: ["- none"]),
|
|
97
|
+
...listOr(plan.diagnostics, renderDiagnostic),
|
|
80
98
|
"",
|
|
81
99
|
"## Recommendations",
|
|
82
|
-
...(plan.recommendations.
|
|
83
|
-
? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`)
|
|
84
|
-
: ["- none"]),
|
|
100
|
+
...listOr(plan.recommendations, (recommendation) => `- [${recommendation.kind}] ${recommendation.message}`),
|
|
85
101
|
"",
|
|
86
102
|
].join("\n");
|
|
87
103
|
|
|
88
|
-
const renderRecommendation = (recommendation: WorkflowApplyReport["recommendations"][number]) => {
|
|
89
|
-
const target = recommendation.target ? ` ${recommendation.target}` : "";
|
|
90
|
-
const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
|
|
91
|
-
return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const renderDiagnostic = (diagnostic: WorkflowApplyReport["diagnostics"][number]) =>
|
|
95
|
-
`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
|
|
96
|
-
|
|
97
|
-
const renderNextAction = (action: WorkflowApplyReport["nextActions"][number]) =>
|
|
98
|
-
`- \`${action.command}\`: ${action.reason}`;
|
|
99
|
-
|
|
100
|
-
const applySourceStatus = (report: WorkflowApplyReport) =>
|
|
101
|
-
report.diagnostics.some(
|
|
102
|
-
(diagnostic) =>
|
|
103
|
-
diagnostic.severity === "error" &&
|
|
104
|
-
(diagnostic.failureScope === "source-change" ||
|
|
105
|
-
!diagnostic.failureScope ||
|
|
106
|
-
diagnostic.failureScope === "unknown"),
|
|
107
|
-
)
|
|
108
|
-
? "failed"
|
|
109
|
-
: "passed";
|
|
110
|
-
|
|
111
104
|
export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
112
105
|
const applySummary = {
|
|
113
106
|
sourceFilesChanged: report.summary?.sourceFilesChanged ?? report.changedFiles,
|
|
@@ -140,7 +133,7 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
|
140
133
|
"",
|
|
141
134
|
`- Mode: ${report.mode}`,
|
|
142
135
|
`- Status: ${report.status}`,
|
|
143
|
-
`- Source-change status: ${
|
|
136
|
+
`- Source-change status: ${sourceBlockers.length ? "failed" : "passed"}`,
|
|
144
137
|
`- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
|
|
145
138
|
`- Source files changed: ${applySummary.sourceFilesChanged.length}`,
|
|
146
139
|
`- Generated files queued for sync: ${applySummary.generatedFilesSynced.length}`,
|
|
@@ -155,24 +148,16 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
|
155
148
|
...(sourceBlockers.length ? sourceBlockers : ["- none"]),
|
|
156
149
|
"",
|
|
157
150
|
"## Automatically Modified",
|
|
158
|
-
...(applySummary.sourceFilesChanged
|
|
159
|
-
? applySummary.sourceFilesChanged.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
160
|
-
: ["- none"]),
|
|
151
|
+
...listOr(applySummary.sourceFilesChanged, renderFile),
|
|
161
152
|
"",
|
|
162
153
|
"## Generated Sync",
|
|
163
|
-
...(applySummary.generatedFilesSynced
|
|
164
|
-
? applySummary.generatedFilesSynced.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
165
|
-
: ["- none"]),
|
|
154
|
+
...listOr(applySummary.generatedFilesSynced, renderFile),
|
|
166
155
|
"",
|
|
167
156
|
"## Applied Commands",
|
|
168
|
-
...(report.appliedCommands
|
|
169
|
-
? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
170
|
-
: ["- none"]),
|
|
157
|
+
...listOr(report.appliedCommands, renderCommand),
|
|
171
158
|
"",
|
|
172
159
|
"## Recommended Validation Commands",
|
|
173
|
-
...(report.recommendedValidationCommands
|
|
174
|
-
? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
175
|
-
: ["- none"]),
|
|
160
|
+
...listOr(report.recommendedValidationCommands, renderCommand),
|
|
176
161
|
"",
|
|
177
162
|
"## User Review Required",
|
|
178
163
|
...(manualReviewItems.length ? manualReviewItems : ["- none"]),
|
|
@@ -185,13 +170,13 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
|
185
170
|
: ["- none"]),
|
|
186
171
|
"",
|
|
187
172
|
"## Diagnostics",
|
|
188
|
-
...(report.diagnostics
|
|
173
|
+
...listOr(report.diagnostics, renderDiagnostic),
|
|
189
174
|
"",
|
|
190
175
|
"## Recommendations",
|
|
191
|
-
...(report.recommendations.
|
|
176
|
+
...listOr(report.recommendations.slice(0, 3), renderRecommendation),
|
|
192
177
|
"",
|
|
193
178
|
"## Next Actions",
|
|
194
|
-
...(report.nextActions.
|
|
179
|
+
...listOr(report.nextActions.slice(0, 3), renderCommand),
|
|
195
180
|
"",
|
|
196
181
|
].join("\n");
|
|
197
182
|
};
|
|
@@ -229,21 +214,16 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
|
|
|
229
214
|
`- Environment: ${report.summary.environment}`,
|
|
230
215
|
"",
|
|
231
216
|
"## Source Change Diagnostics",
|
|
232
|
-
...(report.workflowDiagnostics
|
|
233
|
-
? report.workflowDiagnostics.map(
|
|
234
|
-
(diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
|
|
235
|
-
)
|
|
236
|
-
: ["- none"]),
|
|
217
|
+
...listOr(report.workflowDiagnostics, renderDiagnostic),
|
|
237
218
|
"",
|
|
238
219
|
"## Existing Workspace Blockers",
|
|
239
220
|
`- Status: ${baselineSummary.status}`,
|
|
240
221
|
`- Errors: ${baselineSummary.totalErrors}`,
|
|
241
222
|
`- Warnings: ${baselineSummary.totalWarnings}`,
|
|
242
|
-
...(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
: ["- none"]),
|
|
223
|
+
...listOr(
|
|
224
|
+
baselineSummary.byCode,
|
|
225
|
+
(item) => `- [${item.severity}] ${item.code} (${item.count}x): ${item.sampleMessage}`,
|
|
226
|
+
),
|
|
247
227
|
...(baselineSummary.detailsIncluded
|
|
248
228
|
? []
|
|
249
229
|
: [
|
|
@@ -251,46 +231,30 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
|
|
|
251
231
|
]),
|
|
252
232
|
"",
|
|
253
233
|
"## Existing Workspace Blocker Details",
|
|
254
|
-
...(report.baselineDiagnostics
|
|
255
|
-
? report.baselineDiagnostics.map(
|
|
256
|
-
(diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
|
|
257
|
-
)
|
|
258
|
-
: baselineSummary.detailsIncluded
|
|
259
|
-
? ["- none"]
|
|
260
|
-
: ["- omitted"]),
|
|
234
|
+
...listOr(report.baselineDiagnostics, renderDiagnostic, baselineSummary.detailsIncluded ? "- none" : "- omitted"),
|
|
261
235
|
"",
|
|
262
236
|
"## Known Blockers",
|
|
263
|
-
...(report.knownBlockers
|
|
264
|
-
?
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
})
|
|
270
|
-
: ["- none"]),
|
|
237
|
+
...listOr(report.knownBlockers, (blocker) => {
|
|
238
|
+
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
239
|
+
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
240
|
+
const known = blocker.known ? " known" : "";
|
|
241
|
+
return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
|
|
242
|
+
}),
|
|
271
243
|
"",
|
|
272
244
|
"## Commands",
|
|
273
|
-
...(report.commands
|
|
274
|
-
?
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
})
|
|
278
|
-
: ["- none"]),
|
|
245
|
+
...listOr(report.commands, (command) => {
|
|
246
|
+
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
247
|
+
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
248
|
+
}),
|
|
279
249
|
"",
|
|
280
250
|
"## Diagnostics",
|
|
281
|
-
...(report.diagnostics
|
|
282
|
-
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
283
|
-
: ["- none"]),
|
|
251
|
+
...listOr(report.diagnostics, renderDiagnostic),
|
|
284
252
|
"",
|
|
285
253
|
"## Repair Actions",
|
|
286
|
-
...(report.repairActions
|
|
287
|
-
? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
288
|
-
: ["- none"]),
|
|
254
|
+
...listOr(report.repairActions, renderCommand),
|
|
289
255
|
"",
|
|
290
256
|
"## Next Actions",
|
|
291
|
-
...(report.nextActions
|
|
292
|
-
? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
293
|
-
: ["- none"]),
|
|
257
|
+
...listOr(report.nextActions, renderCommand),
|
|
294
258
|
"",
|
|
295
259
|
].join("\n");
|
|
296
260
|
};
|
|
@@ -306,19 +270,13 @@ export const renderRepairReportMarkdown = (report: RepairReport) =>
|
|
|
306
270
|
`- Target: ${report.target ?? "none"}`,
|
|
307
271
|
"",
|
|
308
272
|
"## Commands",
|
|
309
|
-
...(report.commands.
|
|
310
|
-
? report.commands.map((command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`)
|
|
311
|
-
: ["- none"]),
|
|
273
|
+
...listOr(report.commands, (command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`),
|
|
312
274
|
"",
|
|
313
275
|
"## Diagnostics",
|
|
314
|
-
...(report.diagnostics
|
|
315
|
-
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
316
|
-
: ["- none"]),
|
|
276
|
+
...listOr(report.diagnostics, renderDiagnostic),
|
|
317
277
|
"",
|
|
318
278
|
"## Next Actions",
|
|
319
|
-
...(report.nextActions
|
|
320
|
-
? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
|
|
321
|
-
: ["- none"]),
|
|
279
|
+
...listOr(report.nextActions, renderCommand),
|
|
322
280
|
"",
|
|
323
281
|
].join("\n");
|
|
324
282
|
|
|
@@ -333,3 +291,29 @@ export const renderWorkflowRunArtifact = (artifact: WorkflowRunArtifact, format:
|
|
|
333
291
|
}
|
|
334
292
|
return jsonText(artifact);
|
|
335
293
|
};
|
|
294
|
+
|
|
295
|
+
export const renderPrimitiveWriteReport = (report: PrimitiveWriteReport) =>
|
|
296
|
+
[
|
|
297
|
+
`# Primitive Write: ${report.command}`,
|
|
298
|
+
"",
|
|
299
|
+
`- Status: ${report.status}`,
|
|
300
|
+
"",
|
|
301
|
+
"## Changed Files",
|
|
302
|
+
...listOr(report.changedFiles, renderFile),
|
|
303
|
+
"",
|
|
304
|
+
"## Generated Files",
|
|
305
|
+
...listOr(report.generatedFiles, renderFile),
|
|
306
|
+
"",
|
|
307
|
+
"## Validation Commands",
|
|
308
|
+
...listOr(report.validationCommands, renderCommand),
|
|
309
|
+
"",
|
|
310
|
+
"## Diagnostics",
|
|
311
|
+
...listOr(report.diagnostics, renderDiagnostic),
|
|
312
|
+
"",
|
|
313
|
+
"## Next Actions",
|
|
314
|
+
...listOr(report.nextActions, renderCommand),
|
|
315
|
+
"",
|
|
316
|
+
].join("\n");
|
|
317
|
+
|
|
318
|
+
export const renderPrimitiveReport = (report: PrimitiveWriteReport, format: PrimitiveFormat = "markdown") =>
|
|
319
|
+
format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
|
package/workflow/source.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
import { lowerlize } from "akanjs/common";
|
|
1
2
|
import ts from "typescript";
|
|
2
3
|
import type { Sys } from "../commandDecorators";
|
|
3
4
|
import { generatedFilePathsForTarget } from "./artifacts";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
callExpressionName,
|
|
7
|
+
expressionName,
|
|
8
|
+
firstObjectReturnedByArrow,
|
|
9
|
+
heritageCall,
|
|
10
|
+
nodeName,
|
|
11
|
+
propertyName,
|
|
12
|
+
sourceFileFor,
|
|
13
|
+
} from "./sourceAst";
|
|
5
14
|
import type {
|
|
6
15
|
PrimitiveChangedFile,
|
|
7
16
|
PrimitiveFileMap,
|
|
8
17
|
PrimitiveGeneratedFile,
|
|
9
18
|
PrimitiveNextAction,
|
|
10
19
|
PrimitiveValidationCommand,
|
|
20
|
+
PrimitiveWriteReport,
|
|
11
21
|
WorkflowDiagnostic,
|
|
12
22
|
} from "./types";
|
|
13
23
|
|
|
@@ -57,6 +67,27 @@ export const nextActionsForTarget = (target: string) =>
|
|
|
57
67
|
{ command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." },
|
|
58
68
|
] satisfies PrimitiveNextAction[];
|
|
59
69
|
|
|
70
|
+
export const createPrimitiveWriteReport = ({
|
|
71
|
+
command,
|
|
72
|
+
status,
|
|
73
|
+
changedFiles = [],
|
|
74
|
+
generatedFiles = [],
|
|
75
|
+
validationCommands = [],
|
|
76
|
+
diagnostics = [],
|
|
77
|
+
nextActions = [],
|
|
78
|
+
}: Omit<PrimitiveWriteReport, "schemaVersion" | "status"> & {
|
|
79
|
+
status?: PrimitiveWriteReport["status"];
|
|
80
|
+
}): PrimitiveWriteReport => ({
|
|
81
|
+
schemaVersion: 1,
|
|
82
|
+
command,
|
|
83
|
+
status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
|
|
84
|
+
changedFiles,
|
|
85
|
+
generatedFiles,
|
|
86
|
+
validationCommands,
|
|
87
|
+
diagnostics,
|
|
88
|
+
nextActions,
|
|
89
|
+
});
|
|
90
|
+
|
|
60
91
|
export const createPassedPrimitiveReport = ({
|
|
61
92
|
command,
|
|
62
93
|
changedFiles,
|
|
@@ -90,8 +121,6 @@ export const titleize = (value: string) =>
|
|
|
90
121
|
.replace(/[-_]+/g, " ")
|
|
91
122
|
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
92
123
|
|
|
93
|
-
export const lowerlize = (value: string) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
|
|
94
|
-
|
|
95
124
|
const koLabels: Record<string, string> = {
|
|
96
125
|
amount: "금액",
|
|
97
126
|
budget: "예산",
|
|
@@ -379,9 +408,6 @@ export interface AkanDictionaryStructure {
|
|
|
379
408
|
fields: string[];
|
|
380
409
|
}
|
|
381
410
|
|
|
382
|
-
const sourceFileFor = (fileName: string, content: string, scriptKind = ts.ScriptKind.TS) =>
|
|
383
|
-
ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
384
|
-
|
|
385
411
|
const hasParseDiagnostics = (source: ts.SourceFile) =>
|
|
386
412
|
((source as ts.SourceFile & { parseDiagnostics?: readonly ts.Diagnostic[] }).parseDiagnostics ?? []).length > 0;
|
|
387
413
|
|
|
@@ -398,52 +424,6 @@ const lineEndAt = (content: string, position: number) => {
|
|
|
398
424
|
const lineIndentAt = (content: string, position: number) =>
|
|
399
425
|
/^[ \t]*/.exec(content.slice(lineStartAt(content, position)))?.[0] ?? "";
|
|
400
426
|
|
|
401
|
-
const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
|
|
402
|
-
if (!node) return null;
|
|
403
|
-
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
404
|
-
return null;
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
const propertyName = (node: ts.ObjectLiteralElementLike) =>
|
|
408
|
-
ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
|
|
409
|
-
? nodeName(node.name)
|
|
410
|
-
: null;
|
|
411
|
-
|
|
412
|
-
const expressionName = (expression: ts.Expression): string | null => {
|
|
413
|
-
if (ts.isIdentifier(expression)) return expression.text;
|
|
414
|
-
if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
|
|
415
|
-
if (ts.isCallExpression(expression)) return expressionName(expression.expression);
|
|
416
|
-
if (ts.isAsExpression(expression)) return expressionName(expression.expression);
|
|
417
|
-
return null;
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
|
|
421
|
-
if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
|
|
422
|
-
if (ts.isObjectLiteralExpression(node.body)) return node.body;
|
|
423
|
-
if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
|
|
424
|
-
return node.body.expression;
|
|
425
|
-
}
|
|
426
|
-
if (!ts.isBlock(node.body)) return null;
|
|
427
|
-
for (const statement of node.body.statements) {
|
|
428
|
-
if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
|
|
429
|
-
return statement.expression;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return null;
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const isViaCall = (expression: ts.Expression) =>
|
|
436
|
-
ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
|
|
437
|
-
|
|
438
|
-
const heritageCall = (node: ts.ClassDeclaration) => {
|
|
439
|
-
const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
|
|
440
|
-
const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
|
|
441
|
-
return expression && ts.isCallExpression(expression) ? expression : null;
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
const callExpressionName = (node: ts.CallExpression) =>
|
|
445
|
-
ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
|
|
446
|
-
|
|
447
427
|
const locatedObject = (source: ts.SourceFile, objectLiteral: ts.ObjectLiteralExpression): ObjectInsertionLocator => ({
|
|
448
428
|
objectStart: objectLiteral.getStart(source),
|
|
449
429
|
objectEnd: objectLiteral.getEnd(),
|
|
@@ -869,8 +849,6 @@ export const parseValues = (value: string | null) =>
|
|
|
869
849
|
.map((item) => item.trim())
|
|
870
850
|
.filter(Boolean) ?? [];
|
|
871
851
|
|
|
872
|
-
// ---- Service / signal insertion (add-mutation, add-slice) ----
|
|
873
|
-
|
|
874
852
|
export const hasSourceParseErrors = (content: string, fileName = "source.ts") =>
|
|
875
853
|
hasParseDiagnostics(sourceFileFor(fileName, content));
|
|
876
854
|
|
|
@@ -988,8 +966,7 @@ export const insertSignalFactoryEntry = (
|
|
|
988
966
|
if (!located) return null;
|
|
989
967
|
const locator = locatedObject(source, located.object);
|
|
990
968
|
if (locator.fields.some((field) => field.name === entryName)) return content;
|
|
991
|
-
//
|
|
992
|
-
// object splice (at a higher offset) does not invalidate the param region positions.
|
|
969
|
+
// Apply the param edit last: it sits before the object, so applying it first would shift the object's offsets.
|
|
993
970
|
const paramEdit = factoryParamEdit(content, source, located.arrow, param);
|
|
994
971
|
if (paramEdit === null) return null;
|
|
995
972
|
const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export const sourceFileFor = (fileName: string, content: string) =>
|
|
4
|
+
ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
5
|
+
|
|
6
|
+
export const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
|
|
7
|
+
if (!node) return null;
|
|
8
|
+
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
9
|
+
return null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const propertyName = (node: ts.ObjectLiteralElementLike) =>
|
|
13
|
+
ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
|
|
14
|
+
? nodeName(node.name)
|
|
15
|
+
: null;
|
|
16
|
+
|
|
17
|
+
export const expressionName = (expression: ts.Expression): string | null => {
|
|
18
|
+
if (ts.isIdentifier(expression)) return expression.text;
|
|
19
|
+
if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
|
|
20
|
+
if (ts.isCallExpression(expression)) return expressionName(expression.expression);
|
|
21
|
+
if (ts.isAsExpression(expression)) return expressionName(expression.expression);
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const callExpressionName = (node: ts.CallExpression) =>
|
|
26
|
+
ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
|
|
27
|
+
|
|
28
|
+
export const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
|
|
29
|
+
if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
|
|
30
|
+
if (ts.isObjectLiteralExpression(node.body)) return node.body;
|
|
31
|
+
if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
|
|
32
|
+
return node.body.expression;
|
|
33
|
+
}
|
|
34
|
+
if (!ts.isBlock(node.body)) return null;
|
|
35
|
+
for (const statement of node.body.statements) {
|
|
36
|
+
if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
|
|
37
|
+
return statement.expression;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const isViaCall = (expression: ts.Expression) =>
|
|
44
|
+
ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
|
|
45
|
+
|
|
46
|
+
export const heritageCall = (node: ts.ClassDeclaration) => {
|
|
47
|
+
const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
|
|
48
|
+
const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
|
|
49
|
+
return expression && ts.isCallExpression(expression) ? expression : null;
|
|
50
|
+
};
|
package/workspaceLayout.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
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",
|
|
@@ -43,12 +36,7 @@ export const appRootAllowedDirs = new Set([
|
|
|
43
36
|
"webkit",
|
|
44
37
|
]);
|
|
45
38
|
|
|
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
|
-
*/
|
|
39
|
+
// A library is never booted or packaged as an app, so the run/mobile entries are out; it is published, so README stays.
|
|
52
40
|
export const libRootAllowedFiles = new Set([
|
|
53
41
|
"AGENTS.md",
|
|
54
42
|
"CLAUDE.md",
|
|
@@ -98,9 +86,6 @@ const libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(
|
|
|
98
86
|
export const isAllowedLibFacetRootFile = (filename: string) =>
|
|
99
87
|
libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
|
|
100
88
|
|
|
101
|
-
|
|
102
|
-
* scanSync 는 루트를 `Bun.Glob("*")` 로 읽어 dotfile 을 아예 보지 못한다. 디렉터리를 직접 읽는
|
|
103
|
-
* doctor 가 그 차이만큼 `.DS_Store` 같은 툴 산출물을 에러로 올리므로 같은 기준으로 걸러낸다.
|
|
104
|
-
*/
|
|
89
|
+
// scanSync reads roots through `Bun.Glob("*")`, which skips dotfiles, so doctor skips them the same way.
|
|
105
90
|
export const isScannedRootEntry = (type: SysType, name: string) =>
|
|
106
91
|
!name.startsWith(".") || rootAllowedDirs[type].has(name);
|