scai 0.1.119 โ†’ 0.1.120

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/README.md CHANGED
@@ -41,7 +41,7 @@ Once in the REPL, you can:
41
41
  * Ask questions freely about your codebase:
42
42
 
43
43
  ```text
44
- scai> How many functions in spatialmap.js are missing tests?
44
+ scai> How many functions in config.js are missing tests?
45
45
  scai> Summarize utils/helpers.ts
46
46
  scai> Where are all the database queries defined?
47
47
  scai> List files involved in authentication
@@ -161,7 +161,7 @@ scai auth reset
161
161
 
162
162
  ## ๐Ÿง  Example Queries in REPL
163
163
 
164
- * `Summarize spatialmap.js`
164
+ * `Summarize codeTransform.js`
165
165
  * `Explain utils/helpers.ts architecture`
166
166
  * `List all functions without tests in services/`
167
167
  * `Show where database queries are defined`
@@ -12,49 +12,69 @@ import { semanticAnalysisStep } from "./semanticAnalysisStep.js";
12
12
  import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
13
13
  import { transformPlanGenStep } from "./transformPlanGenStep.js";
14
14
  import { finalPlanGenStep } from "./finalPlanGenStep.js";
15
- /** Build a registry of all built-in modules */
15
+ import { Spinner } from "../lib/spinner.js";
16
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
17
+ function startTimer() {
18
+ const start = Date.now();
19
+ return () => Date.now() - start;
20
+ }
21
+ function logLine(phase, step, ms, desc) {
22
+ // Clear current line (removes leftover spinner)
23
+ process.stdout.write('\r\x1b[K');
24
+ const suffix = desc ? ` โ€” ${desc}` : "";
25
+ const timing = typeof ms === "number" ? ` (${ms}ms)` : "";
26
+ console.log(`[AGENT] ${phase} :: ${step}${suffix}${timing}`);
27
+ }
28
+ /** Helper to display messages to the user from the agent */
29
+ function userOutput(message) {
30
+ console.log(`[USER OUTPUT] ${message}`);
31
+ }
32
+ /** Helper to display messages with phase context */
33
+ function userPhaseOutput(phase, message) {
34
+ console.log(`[USER OUTPUT] [${phase}] ${message}`);
35
+ }
36
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ registry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
16
37
  const MODULE_REGISTRY = Object.fromEntries(Object.entries(builtInModules).map(([name, mod]) => [name, mod]));
17
- /** Resolve a module from the registry */
18
38
  function resolveModuleForAction(action) {
19
- const mod = MODULE_REGISTRY[action];
20
- if (!mod)
21
- console.warn(`โš ๏ธ Missing module for action "${action}" โ€” skipping`);
22
- return mod;
39
+ return MODULE_REGISTRY[action];
23
40
  }
41
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ agent โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
24
42
  export class MainAgent {
25
43
  constructor(context) {
44
+ this.spinner = new Spinner(); // spinner for user feedback
26
45
  this.runCount = 0;
27
46
  this.maxRuns = 2;
28
47
  this.context = context;
29
- this.query = context.initContext?.userQuery ?? '';
48
+ this.query = context.initContext?.userQuery ?? "";
30
49
  }
31
- /** Generic step executor */
50
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ step executor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
32
51
  async executeStep(step, input) {
33
- console.log(`\n`);
34
- console.log(`\n =====================================================================================`);
35
- console.log(`\nโšก Executing step: ${step.action}`);
36
- if (step.description)
37
- console.log(`\n --> Description: ${step.description}`);
38
- console.log(`\n =====================================================================================`);
39
- // attach the current step to the context
52
+ const stop = startTimer();
40
53
  if (input.context) {
41
- input.context.currentStep = step; // <-- new property
54
+ input.context.currentStep = step;
42
55
  }
43
56
  const mod = resolveModuleForAction(step.action);
44
57
  if (!mod) {
45
- return { query: input.query, content: input.content, data: { skipped: true }, context: input.context };
58
+ logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
59
+ return {
60
+ query: input.query,
61
+ content: input.content,
62
+ data: { skipped: true },
63
+ context: input.context
64
+ };
46
65
  }
47
- const moduleInput = {
48
- query: step.description ?? input.query,
49
- content: input.data ?? input.content,
50
- context: input.context // shared reference
51
- };
52
66
  try {
53
- console.log(` โ–ถ Running module: ${mod.name}`);
54
- const output = await mod.run(moduleInput);
55
- if (!output)
56
- throw new Error(`Module '${mod.name}' returned empty output`);
57
- // Return only what matters โ€” context is always the same reference
67
+ // Update spinner text for user
68
+ this.spinner.update(`Running step: ${step.action}`);
69
+ const output = await mod.run({
70
+ query: step.description ?? input.query,
71
+ content: input.data ?? input.content,
72
+ context: input.context
73
+ });
74
+ if (!output) {
75
+ throw new Error(`Module "${mod.name}" returned empty output`);
76
+ }
77
+ logLine("EXECUTE", step.action, stop(), step.description);
58
78
  return {
59
79
  query: step.description ?? input.query,
60
80
  data: output.data,
@@ -62,194 +82,126 @@ export class MainAgent {
62
82
  };
63
83
  }
64
84
  catch (err) {
65
- console.error(` โŒ Module "${mod?.name}" failed:`, err);
66
- return {
67
- query: input.query,
68
- content: input.content,
69
- data: { error: String(err) },
70
- context: input.context
71
- };
85
+ logLine("EXECUTE", step.action, stop(), "failed");
86
+ throw err;
72
87
  }
73
88
  }
74
- /** Main run sequence */
89
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ main run โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
75
90
  async run() {
76
- var _a;
77
91
  this.runCount++;
78
- console.log(`\n =====================================================================================`);
79
- console.log(`๐Ÿš€ ================= [AutonomousAgent] Starting run #${this.runCount} =================`);
92
+ const stopRun = startTimer();
93
+ logLine("RUN", `start #${this.runCount}`);
80
94
  logInputOutput("GlobalContext (structured)", "input", this.context);
81
- // ---------------------------------------------------------------------
82
- // GENERATE FOLDER-CAPSULE SUMMARIES
83
- // ---------------------------------------------------------------------
84
- if (this.context.initContext?.folderCapsules?.length) {
85
- const capsulesSummary = this.context.initContext.folderCapsules.map(fc => ({
86
- path: fc.path,
87
- fileCount: fc.stats?.fileCount ?? 0,
88
- depth: fc.depth ?? 0,
89
- confidence: fc.confidence ?? 0,
90
- roles: fc.roles ?? [],
91
- concerns: fc.concerns ?? []
92
- }));
93
- // Add a human-readable summary for the LLM
94
- (_a = this.context).analysis ?? (_a.analysis = {});
95
- this.context.analysis.folderCapsulesSummary = capsulesSummary;
96
- const humanReadable = capsulesSummary.map(fc => `- ${fc.path}: ${fc.fileCount} files, depth ${fc.depth}, confidence ${fc.confidence}`).join('\n');
97
- this.context.analysis.folderCapsulesHuman = humanReadable;
98
- console.log('\n๐Ÿ“‚ Folder Capsules Summary (for agent steps):\n', humanReadable);
99
- }
100
- // ---------------------------------------------------------------------
101
- // 0๏ธโƒฃ UNDERSTAND INTENT โ€” First-pass interpretation of query
102
- // ---------------------------------------------------------------------
103
- console.log("\n๐Ÿง  Running understandIntentStep (pre-analysis fixture)\n");
104
- await understandIntentStep.run({
105
- context: this.context
106
- });
107
- // ---------------------------------------------------------------------
108
- // 1๏ธโƒฃ PLAN RESOLVER โ€” Fast path (context-driven routing)
109
- // ---------------------------------------------------------------------
110
- await planResolverStep.run(this.context);
111
- // --------------------------------------------------
112
- // ๐Ÿšฆ ROUTING DECISION
113
- // --------------------------------------------------
95
+ this.spinner.start(); // start spinner at beginning
96
+ /* BOOT */
97
+ {
98
+ const t = startTimer();
99
+ await understandIntentStep.run({ context: this.context });
100
+ logLine("BOOT", "understandIntent", t());
101
+ }
102
+ /* ROUTE */
103
+ {
104
+ const t = startTimer();
105
+ await planResolverStep.run(this.context);
106
+ logLine("ROUTE", "planResolver", t());
107
+ }
114
108
  const routing = this.context.analysis?.routingDecision;
115
- // โœ… FAST PATH: if planResolver thinks we have a final answer, wrap it in ModuleIO and return
116
- if (routing?.decision === 'final-answer' && routing.answer) {
117
- console.log("โšก Fast path hit โ€” returning final answer directly");
109
+ if (routing?.decision === "final-answer" && routing.answer) {
110
+ this.spinner.stop();
111
+ logLine("RUN", "complete", stopRun());
112
+ userOutput("All input/output logs can be found at ~/.scai/input_output.log");
118
113
  return {
119
114
  query: this.query,
120
115
  data: {
121
116
  finalAnswer: routing.answer,
122
- source: "planResolver",
117
+ source: "planResolver"
123
118
  },
124
119
  context: this.context
125
120
  };
126
121
  }
127
- // --------------------------------------------------
128
- // โฉ FALL THROUGH: continue with plan generation
129
- // --------------------------------------------------
130
- let textPlan;
131
- // Use rationale from routing if available (temporary compatibility)
132
- if (typeof routing?.rationale === 'string' && routing.rationale.trim()) {
133
- textPlan = routing.rationale;
134
- }
135
- // Fallback: use the original query if no plan/rationale is available
136
- if (!textPlan || textPlan.trim().length === 0) {
137
- textPlan = this.query;
138
- }
139
- // textPlan can now be passed to planGeneratorStep
140
- // ---------------------------------------------------------------------
141
- // PRE-FILE-SEARCH CHECK (do we have the files required to continue )
142
- // ---------------------------------------------------------------------
143
- await preFileSearchCheckStep(this.context);
144
- // ---------------------------------------------------------------------
145
- // 2๏ธโƒฃ INFO PLAN GENERATION (information-only steps)
146
- // ---------------------------------------------------------------------
147
- await infoPlanGen.run(this.context);
148
- const infoPlan = this.context?.analysis?.planSuggestion?.plan ?? { steps: [] };
149
- const infoSteps = infoPlan.steps.filter(s => s.groups?.includes("info"));
150
- console.log("information gathering steps:\n");
151
- console.dir(infoSteps);
152
- let stepIO = {
153
- query: this.query,
154
- context: this.context
155
- };
156
122
  // =====================================================
157
123
  // INFORMATION ACQUISITION PHASE
158
124
  // Purpose: gather raw information, no interpretation
159
125
  // =====================================================
160
- //let count = 0;
161
- for (const step of infoSteps) {
162
- //count++;
126
+ {
127
+ const t = startTimer();
128
+ await preFileSearchCheckStep(this.context);
129
+ logLine("PRECHECK", "preFileSearch", t());
130
+ }
131
+ {
132
+ const t = startTimer();
133
+ await infoPlanGen.run(this.context);
134
+ logLine("PLAN", "infoPlanGen", t());
135
+ }
136
+ const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
137
+ let stepIO = {
138
+ query: this.query,
139
+ context: this.context
140
+ };
141
+ for (const step of infoPlan.steps.filter(s => s.groups?.includes("info"))) {
163
142
  stepIO = await this.executeStep(step, stepIO);
164
- /* if (count === 4) {
165
- debugContext(this.context, {
166
- step: "first step",
167
- note: "searchFiles?"
168
- });
169
- } */
170
143
  }
171
144
  // =====================================================
172
145
  // ANALYSIS PHASE
173
146
  // Purpose: understand what we have and what is being asked
174
147
  // =====================================================
175
- // ๐ŸŽฏ RELEVANT SOURCE SELECTION (authoritative narrowing)
176
- console.log("\n๐Ÿ“ Selecting relevant source files\n");
177
- await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
178
- // ANALYSIS PHASE (deterministic, durable)
179
- console.log("\n๐Ÿ—๏ธ Running structural analysis\n");
180
- await structuralAnalysisStep.run({ query: this.query, context: this.context });
181
- // SEMANTIC ANALYSIS PHASE (LLM-driven)
182
- console.log("\n๐Ÿง  Running semantic analysis\n");
183
- await semanticAnalysisStep.run({ query: this.query, context: this.context });
184
- // SEMANTIC PLAN TARGET FILES (moves focus -> plan.targetFiles)
185
- console.log("\n๐Ÿง  Running plan target files\n");
186
- await planTargetFilesStep.run({ query: this.query, context: this.context });
187
- /* debugContext(this.context, {
188
- step: "Semantic analysis step",
189
- note: "semantic meaning?"
190
- }); */
191
- // VALIDATION PHASE
192
- console.log("\n๐Ÿง  Running validation analysis\n");
193
- await validationAnalysisStep.run({ query: this.query, context: this.context });
194
- // =====================================================
195
- // ROUTING / PLANNING PHASE
196
- // =====================================================
148
+ {
149
+ let t = startTimer();
150
+ await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
151
+ logLine("ANALYSIS", "selectRelevantSources", t());
152
+ t = startTimer();
153
+ await structuralAnalysisStep.run({ query: this.query, context: this.context });
154
+ logLine("ANALYSIS", "structuralAnalysis", t());
155
+ t = startTimer();
156
+ await semanticAnalysisStep.run({ query: this.query, context: this.context });
157
+ logLine("ANALYSIS", "semanticAnalysis", t());
158
+ t = startTimer();
159
+ await planTargetFilesStep.run({ query: this.query, context: this.context });
160
+ logLine("ANALYSIS", "planTargetFiles", t());
161
+ t = startTimer();
162
+ await validationAnalysisStep.run({ query: this.query, context: this.context });
163
+ logLine("VALIDATE", "validationAnalysis", t());
164
+ }
197
165
  const review = await contextReviewStep(this.context);
198
166
  if (review.decision === "loopAgain" && this.runCount < this.maxRuns) {
199
- console.log("๐Ÿ”„ Looping for additional context");
200
- // Clear noisy bootstrap artifacts before looping
201
167
  this.resetInitContextForLoop();
202
168
  return this.run();
203
169
  }
204
170
  // =====================================================
205
- // TRANSFORM PLAN GENERATION (after context review)
206
- // Purpose: produce actionable transform steps
207
- // =====================================================
208
- console.log("\n๐Ÿ› ๏ธ Generating transform plan\n");
209
- await transformPlanGenStep.run(this.context);
210
- // Filter transform steps
211
- const transformPlan = this.context?.analysis?.planSuggestion?.plan ?? { steps: [] };
212
- const transformSteps = transformPlan.steps.filter(s => s.groups?.includes("transform"));
213
- console.log("transform steps:\n");
214
- console.dir(transformSteps);
215
- // =====================================================
216
171
  // TRANSFORM PHASE
217
172
  // Purpose: produce concrete changes or artifacts
218
173
  // =====================================================
219
- console.log("\nโšก Running transform steps\n");
220
- for (const step of transformSteps) {
174
+ {
175
+ const t = startTimer();
176
+ await transformPlanGenStep.run(this.context);
177
+ logLine("PLAN", "transformPlanGen", t());
178
+ }
179
+ const transformPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
180
+ for (const step of transformPlan.steps.filter(s => s.groups?.includes("transform"))) {
221
181
  stepIO = await this.executeStep(step, stepIO);
222
182
  }
223
183
  // =====================================================
224
- // FINAL PLAN GENERATION
225
- // Purpose: produce finalize / commit steps
226
- // =====================================================
227
- console.log("\nโœ… Generating final plan\n");
228
- await finalPlanGenStep.run(this.context);
229
- // Filter finalize steps
230
- const finalPlan = this.context?.analysis?.planSuggestion?.plan ?? { steps: [] };
231
- const finalizeSteps = finalPlan.steps.filter(s => s.groups?.includes("finalize"));
232
- console.log("finalize steps:\n");
233
- console.dir(finalizeSteps);
234
- // =====================================================
235
184
  // FINALIZE PHASE
236
185
  // Purpose: commit results and respond to the user
237
186
  // =====================================================
238
- for (const step of finalizeSteps) {
187
+ {
188
+ const t = startTimer();
189
+ await finalPlanGenStep.run(this.context);
190
+ logLine("PLAN", "finalPlanGen", t());
191
+ }
192
+ const finalPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
193
+ for (const step of finalPlan.steps.filter(s => s.groups?.includes("finalize"))) {
239
194
  stepIO = await this.executeStep(step, stepIO);
240
195
  }
241
- console.log("๐Ÿ Agent completed.");
242
- console.log("---------------------------------------------\n");
243
- console.log("Show final context\n", this.context);
196
+ this.spinner.stop(); // stop spinner at the end
197
+ userOutput("All input/output logs can be found at ~/.scai/input_output.log");
198
+ logLine("RUN", "complete", stopRun());
244
199
  return stepIO;
245
200
  }
246
- // =====================================================
247
- // Helper: reset initContext for clean loop
248
- // =====================================================
201
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
249
202
  resetInitContextForLoop() {
250
203
  if (this.context.initContext) {
251
204
  this.context.initContext.relatedFiles = [];
252
- console.log(" ๐Ÿงน Cleared initContext.relatedFiles for clean loop");
253
205
  }
254
206
  }
255
207
  }
@@ -36,7 +36,6 @@ export const infoPlanGen = {
36
36
  // --------------------------------------------------
37
37
  const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('info'));
38
38
  const actionsJson = JSON.stringify(effectiveActions, null, 2);
39
- console.log('Actions: ', actionsJson);
40
39
  const intentText = context.analysis.intent?.normalizedQuery ??
41
40
  context.initContext?.userQuery ??
42
41
  '';
@@ -45,7 +45,6 @@ PLAN:
45
45
  content: prompt,
46
46
  };
47
47
  const genOutput = await generate(genInput);
48
- console.log('PlanResolverOutput: ', genInput.data);
49
48
  const raw = String(genOutput.data ?? '').trim();
50
49
  logInputOutput('planResolver raw', 'output', raw);
51
50
  const isResolved = raw.startsWith('RESOLVED:');
@@ -1,6 +1,5 @@
1
1
  // File: src/modules/selectRelevantSourcesStep.ts
2
2
  import fs from "fs";
3
- import chalk from "chalk";
4
3
  import { generate } from "../lib/generate.js";
5
4
  import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
6
5
  import { logInputOutput } from "../utils/promptLogHelper.js";
@@ -14,7 +13,6 @@ export const selectRelevantSourcesStep = {
14
13
  if (!context) {
15
14
  throw new Error("[selectRelevantSources] StructuredContext is required.");
16
15
  }
17
- console.log(chalk.blueBright(`๐Ÿ“ [selectRelevantSources] Selecting relevant files for query "${query}"...`));
18
16
  // Merge candidate paths (relatedFiles + existing workingFiles)
19
17
  // At this point in-depth context build or searchfiles may have placed files in
20
18
  // working files.
@@ -66,7 +66,6 @@ Return a strictly valid JSON plan:
66
66
  }
67
67
  `.trim();
68
68
  try {
69
- console.log('TransformPlanGenStep prompt', prompt);
70
69
  const genInput = { query: intentText, content: prompt };
71
70
  const genOutput = await generate(genInput);
72
71
  const raw = typeof genOutput.data === 'string'
@@ -1,5 +1,4 @@
1
1
  // File: lib/generate.ts
2
- import { Spinner } from './spinner.js';
3
2
  import { Config, readConfig } from '../config.js';
4
3
  import { startModelProcess } from '../utils/checkModel.js';
5
4
  /**
@@ -18,20 +17,18 @@ export async function generate(input) {
18
17
  : JSON.stringify(input.content, null, 2))
19
18
  : "";
20
19
  const prompt = `${queryPart}${contentPart}`.trim();
21
- const spinner = new Spinner();
22
- spinner.start();
23
20
  try {
24
- const data = await doGenerate(prompt, model, contextLength, spinner, false);
21
+ const data = await doGenerate(prompt, model, contextLength, false);
25
22
  return { query: input.query, data };
26
23
  }
27
24
  catch {
28
- spinner.fail('Model request failed. Attempting restart...');
25
+ // Attempt model restart if first generation fails
29
26
  await startModelProcess();
30
- const data = await doGenerate(prompt, model, contextLength, spinner, true);
27
+ const data = await doGenerate(prompt, model, contextLength, true);
31
28
  return { query: input.query, data };
32
29
  }
33
30
  }
34
- async function doGenerate(prompt, model, contextLength, spinner, retrying) {
31
+ async function doGenerate(prompt, model, contextLength, retrying) {
35
32
  const res = await fetch('http://localhost:11434/api/generate', {
36
33
  method: 'POST',
37
34
  headers: { 'Content-Type': 'application/json' },
@@ -48,14 +45,5 @@ async function doGenerate(prompt, model, contextLength, spinner, retrying) {
48
45
  throw new Error(`Model request failed with status ${res.status}`);
49
46
  }
50
47
  const data = await res.json();
51
- if (retrying) {
52
- spinner.succeed('Model response received after restart.');
53
- }
54
- else {
55
- spinner.succeed();
56
- }
57
- process.stdout.write('\n');
58
- return data.response?.trim() ?? '';
59
- process.stdout.write('\n');
60
48
  return data.response?.trim() ?? '';
61
49
  }
@@ -60,7 +60,6 @@ export const writeFileModule = {
60
60
  }
61
61
  try {
62
62
  await fs.writeFile(filePath, f.content, "utf-8");
63
- console.log(chalk.green(`โœ… Written: ${filePath}`));
64
63
  writtenFiles.push(filePath);
65
64
  }
66
65
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.119",
3
+ "version": "0.1.120",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"